Modern CSS Solutions A series examining modern CSS solutions to problems Stephanie Eckles (@5t3ph), a seasoned frontend developer, has been solving for 15+ years. 2024-07-19T00:00:00Z https://moderncss.dev Stephanie Eckles Providing Type Definitions for CSS with @property 2024-07-19T00:00:00Z https://moderncss.dev/providing-type-definitions-for-css-with-at-property/ <p>A cross-browser feature as of the release of Firefox 128 in July 2024 is a new at-rule - <code>@property</code> - which allows defining types as well as inheritance and an initial value for your custom properties.</p> <p>We'll learn when and why traditional fallback values can fail, and how <code>@property</code> features allow us to write safer, more resilient CSS custom property definitions.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="standard-use-of-custom-properties">Standard Use of Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#standard-use-of-custom-properties" aria-labelledby="standard-use-of-custom-properties"><span hidden="">#</span></a></div> <p>Custom properties - aka &quot;CSS variables&quot; - are useful because they allow creating references to values similar to variables in other programming languages.</p> <p>Consider the following scenario which creates and assigns the <code>--color-blue</code> property, which then is implemented as a class and applied to a paragraph.</p> <details open=""> <summary>CSS for "Standard use case for custom properties"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-270 { --color-blue: blue; padding: 0.5rem; } .color-blue-270 { color: var(--color-blue); } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-270"> <p class="color-blue-270">I'm blue dabadee</p> </div> </div> </div> <p>The paragraph renders as blue. Excellent! Ship it.</p> <div class="heading-wrapper h2"> <h2 id="common-error-conditions-using-custom-properties">Common Error Conditions Using Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#common-error-conditions-using-custom-properties" aria-labelledby="common-error-conditions-using-custom-properties"><span hidden="">#</span></a></div> <p>Now, you and I both know that &quot;blue&quot; is a color. And it also may seem obvious that you would only apply the class that uses this color to text we intend to be <em>blue</em>.</p> <p>But, the real world isn't perfect, and sometimes the downstream consumers of our CSS end-up with a reason to re-define the value. Or perhaps they accidentally introduce a typo that impacts the original value.</p> <p>The outcome of either of these scenarios could be:</p> <ul> <li>the text ends up a color besides blue, as that author intended</li> <li>the text surprisingly renders as black</li> </ul> <p>If the rendered color is surprisingly black, it's likely that we've hit the unique scenario of <em>invalid at computed value time</em>.</p> <p>When the browser is assessing CSS rules and working out what value to apply to each property based on the cascade, inheritance, specificity and so forth, it will retain a custom property as the winning value as long as it understands the general way it's being used.</p> <p>In our <code>--color-blue</code> example, the browser definitely understands the <code>color</code> property, so it assumes all will be ok with the use of the variable as well.</p> <p>But, what happens if someone redefines <code>--color-blue</code> to an invalid color?</p> <details open=""> <summary>CSS for "Defining an invalid color"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-389 { --color-blue: blue; padding: 0.5rem; } .color-blue-389 { color: var(--color-blue); } .demo-389 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-389"> <p class="color-blue-389">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Uh oh - it's surprisingly rendering as black.</p> <div class="heading-wrapper h2"> <h2 id="why-traditional-fallbacks-can-fail">Why Traditional Fallbacks Can Fail</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#why-traditional-fallbacks-can-fail" aria-labelledby="why-traditional-fallbacks-can-fail"><span hidden="">#</span></a></div> <p>Ok, before we learn what that scary-sounding phrase means, let's take a look in DevTools and see if it gives us a clue about what's going on.</p> <p><img src="https://moderncss.dev/img/posts/35/devtools-notacolor.png" alt="Styles panel in DevTools shows .color-blue and the paragraph rule, with no error apparent" /></p> <p>That looks pretty normal, and doesn't seem to reveal that anything is wrong, making troubleshooting the error a lot trickier.</p> <p>You might know that custom properties allow a fallback value as second parameter, so perhaps that will help! Let's try.</p> <details open=""> <summary>CSS for "Attempt resolution with custom property fallback"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">,</span> blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-371 { --color-blue: blue; padding: 0.5rem; } .color-blue-371 { color: var(--color-blue, blue); } .demo-371 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-371"> <p class="color-blue-371">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Unfortunately, the text still renders as black.</p> <p>Ok, but our good friend the cascade exists, and back in the day we used to put things like vendor prefixed properties prior to the unprefixed ones. So, perhaps if we use a similar method and supply an extra <code>color</code> definition before the one that has the custom property it can fallback to that?</p> <details open=""> <summary>CSS for "Attempt resolution with extra color definition"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-107 { --color-blue: blue; padding: 0.5rem; } .color-blue-107 { color: blue; color: var(--color-blue); } .demo-107 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-107"> <p class="color-blue-107">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Bummer, we don't seem to be making progress towards preventing this issue.</p> <p>This is because of the (slightly scary sounding) scenario <em>invalid at computed value time</em>.</p> <p>Although the browser has kept our definition that expects a custom property value, it's not until later that the browser tries to actually compute that value.</p> <p>In this case, it looks at both the <code>.color-blue</code> class and the value provided for the <code>p</code> element rule and attempts to apply the computed value of <code>notacolor</code>. At this stage, it has discarded the alternate value of <code>blue</code> originally provided by the class. Consequently, since <code>notacolor</code> is in fact not a color and therefore <em>invalid</em>, the best it can do is use either:</p> <ul> <li>an <em>inherited value</em> if the property is allowed to inherit and an ancestor has provided a value; or</li> <li>the <em>initial value</em> as defined in the CSS spec</li> </ul> <p>While <code>color</code> is an inheritable property, we haven't defined it on any ancestors, so the rendered color of <code>black</code> is due to the <em>initial</em> value.</p> <blockquote> <p>Refer to this earlier Modern CSS article about <a href="https://moderncss.dev/how-custom-property-values-are-computed/">how custom property values are computed</a> and learn more deeply about the condition of invalid at computed value time.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="defining-types-for-safer-css">Defining Types for Safer CSS</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#defining-types-for-safer-css" aria-labelledby="defining-types-for-safer-css"><span hidden="">#</span></a></div> <p>It's time to introduce <code>@property</code> to help solve this issue of what you may perceive as a surprising rendered value.</p> <p>The critical features <code>@property</code> provides are:</p> <ul> <li>defining acceptable types for specific custom properties</li> <li>enabling or disabling inheritance</li> <li>providing an initial value as a failsafe for invalid or undefined values</li> </ul> <p>This at-rule is defined on a per-custom property basis, meaning a unique definition is needed for each property for which you want to leverage these benefits.</p> <p>It is <em>not</em> a requirement, and you can certainly continue using custom properties without ever bringing in <code>@property</code>.</p> <blockquote> <p>Please note that at time of writing, <code>@property</code> is very newly cross-browser and I would advise you to consider it a progressive enhancement to benefit users in supporting browsers.</p> </blockquote> <p>Let's apply it to our blue dilemma and see how it fixes the issue of the otherwise invalid color supplied in the element rule.</p> <details open=""> <summary>CSS for "Apply @property to --color-blue"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-blue</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Prior rules also apply */</span> </code></pre> </details> <style> @property --c-blue { syntax: "<color>"; inherits: true; initial-value: blue; } .demo-672 { --c-blue: blue; padding: 0.5rem; } .color-blue-672 { color: var(--c-blue); } .demo-672 p { --c-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-672"> <p class="color-blue-672">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Success, our text is still blue despite the invalid definition!</p> <p>Additionally, DevTools is now helpful again:</p> <p><img src="https://moderncss.dev/img/posts/35/devtools-with-at-property.png" alt="DevTools displays the invalid value within the paragraph rule as crossed out and with an error icon, and also provides a hover overlay for the --color-blue custom property with the full definition provided in @property" /></p> <p>We can observe both that the invalid value is clearly an error, and we also are provided the full definition of the custom property via the hover overlay.</p> <div class="heading-wrapper h2"> <h2 id="providing-types-via-syntax">Providing Types via <code>syntax</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#providing-types-via-syntax" aria-labelledby="providing-types-via-syntax"><span hidden="">#</span></a></div> <p>Why would we need types for custom properties? Here are a few reasons:</p> <ul> <li>types help verify what makes a valid vs. invalid value</li> <li>without types, custom properties are very open-ended and can take nearly any value, including a blank space</li> <li>lack of types prevents browser DevTools from providing the optimal level of detail about which value is in use for a custom property</li> </ul> <p>In our <code>@property</code> definition, the <code>syntax</code> descriptor enables providing the allowed types for the custom property. We used <code>&quot;&lt;color&gt;&quot;</code>, but other types include:</p> <ul> <li><code>&quot;&lt;length&gt;&quot;</code> - numbers with units attached, ex. <code>4px</code> or <code>3vw</code></li> <li><code>&quot;&lt;integer&gt;&quot;</code> - decimal units 0 through 9 (aka &quot;whole numbers&quot;)</li> <li><code>&quot;&lt;number&gt;&quot;</code> - numbers which may have a fraction, ex. <code>1.25</code></li> <li><code>&quot;&lt;percentage&gt;&quot;</code> - numbers with a percentage sign attached, ex. <code>24%</code></li> <li><code>&quot;&lt;length-percentage&gt;&quot;</code> - accepts valid <code>&lt;length&gt;</code> or <code>&lt;percentage&gt;</code> values</li> </ul> <p>A special case is <code>&quot;*&quot;</code> which stands for &quot;universal syntax&quot; and enables accepting any value, similar to the default behavior. This means you skip the typing benefit, but perhaps want the inheritance and/or initial value control.</p> <p>These types and more are listed for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax">the syntax descriptor on MDN</a>.</p> <p>The type applies to the <em>computed value</em> of the custom property, so the <code>&quot;&lt;color&gt;&quot;</code> type would be happy with both <code>blue</code> as well as <code>light-dark(blue, cyan)</code> (although only one of those is accepted into the <code>initial-value</code> as we will soon learn).</p> <div class="heading-wrapper h3"> <h3 id="stronger-typing-with-lists">Stronger Typing With Lists</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#stronger-typing-with-lists" aria-labelledby="stronger-typing-with-lists"><span hidden="">#</span></a></div> <p>Let's say we want to provide a little flexibility for our <code>--color-blue</code> custom property.</p> <p>We can use a list to provide valid options. Anything other than these <em>exact</em> values would be considered invalid, and use the <code>initial-value</code> instead (if inheritance didn't apply). These are called &quot;custom idents&quot;, are case sensitive, and can be any value.</p> <details open=""> <summary>CSS for "Defining a list within syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-blue</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"blue | cyan | dodgerblue"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.demo p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> dodgerblue<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --c2-blue { syntax: "blue | cyan | dodgerblue"; inherits: true; initial-value: blue; } .demo-396 { padding: 0.5rem; } .color-blue-396 { color: var(--c2-blue); } .demo-396 p { --c2-blue: dodgerblue; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-396"> <p class="color-blue-396">I'm blue dabadee (maybe)</p> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="typing-for-mixed-values">Typing for Mixed Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-mixed-values" aria-labelledby="typing-for-mixed-values"><span hidden="">#</span></a></div> <p>The pipe character (<code>|</code>) used in the previous list indicates an &quot;or&quot; condition. While we used explicit color names, it can also be used to say &quot;any of these syntax types are valid.&quot;</p> <pre class="language-css"><code class="language-css"><span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color> | &lt;length>"</span><span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="typing-for-multiple-values">Typing for Multiple Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-multiple-values" aria-labelledby="typing-for-multiple-values"><span hidden="">#</span></a></div> <p>So far, we've only typed custom properties that expect a <em>single</em> value.</p> <p>Two additional cases can be covered with an additional &quot;multiplier&quot; character, which should immediately follow the syntax component name.</p> <ul> <li>Use <code>+</code> to support a space-separated list, ex. <code>&quot;&lt;length&gt;+&quot;</code></li> <li>Use <code>#</code> to support a comma-separated list, ex. <code>&quot;&lt;length&gt;#&quot;</code></li> </ul> <p>This can be useful for properties that allow multiple definitions, such as <code>background-image</code>.</p> <details open=""> <summary>CSS for "Support multiple values for syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --bg-gradient</span><span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;image>#"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>to right<span class="token punctuation">,</span> blue 10px 12px<span class="token punctuation">,</span> transparent 12px 22px<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>to bottom<span class="token punctuation">,</span> blue 10px 12px<span class="token punctuation">,</span> transparent 12px 22px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 5rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --bg-gradient{ syntax: "<image>#"; inherits: false; initial-value: repeating-linear-gradient(to right, blue 10px 12px, transparent 12px 22px), repeating-linear-gradient(to bottom, blue 10px 12px, transparent 12px 22px); } .box-310 { background-image: var(--bg-gradient); inline-size: 5rem; aspect-ratio: 1; border-radius: 4px; border: 1px solid; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-310"></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="typing-for-multi-part-mixed-values">Typing for Multi-Part Mixed Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-multi-part-mixed-values" aria-labelledby="typing-for-multi-part-mixed-values"><span hidden="">#</span></a></div> <p>Some properties accept mixed types to develop the full value, such as <code>box-shadow</code> which has potential types of <code>inset</code>, a series of <code>&lt;length&gt;</code> values, and a <code>&lt;color&gt;</code>.</p> <p>Presently, it's not possible to type this in a single <code>@property</code> definition, although you may attempt to try something like <code>&quot;&lt;length&gt;+ &lt;color&gt;&quot;</code>. However, this effectively invalidates the <code>@property</code> definition itself.</p> <p>One alternative is to break up the custom property definitions so that we can allow a series of lengths, and then allow a color. While slightly more cumbersome, this allows us to still get the benefit of typing which relieves the potential errors we covered earlier.</p> <details open=""> <summary>CSS for "Support multi-part mixed values for syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-shadow-length</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length>+"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0px 0px 8px 2px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --box-shadow-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 75%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-shadow-length<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-shadow-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 5rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --box-shadow-length { syntax: "<length>+"; inherits: false; initial-value: 0px 0px 8px 2px; } @property --box-shadow-color { syntax: "<color>"; inherits: false; initial-value: hsl(0 0% 75%); } .box-707 { box-shadow: var(--box-shadow-length) var(--box-shadow-color); inline-size: 5rem; aspect-ratio: 1; border-radius: 4px; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-707"></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="allowing-any-type">Allowing Any Type</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#allowing-any-type" aria-labelledby="allowing-any-type"><span hidden="">#</span></a></div> <p>If you're less concerned about the &quot;type&quot; of a property for something like <code>box-shadow</code> and care more about inheritance or the initial value, you can instead use the universal syntax definition to allow any value. This negates the problem we just mitigated by splitting up the parts.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-shadow</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"*"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0px 0px 8px 2px <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 75%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Because the universal syntax accepts any value, an additional &quot;multiplier&quot; is not needed.</p> <p><em>Note</em>: The <code>initial-value</code> is still required to be <em>computationally independent</em> as we'll learn about soon under <a href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#limitations-of-initial-value">limitations of initial-value</a>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="modifying-inheritance">Modifying Inheritance</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#modifying-inheritance" aria-labelledby="modifying-inheritance"><span hidden="">#</span></a></div> <p><a href="https://web.dev/learn/css/inheritance#which_properties_are_inherited_by_default">A subset of CSS properties are inheritable</a>, such as <code>color</code>. The <code>inherits</code> descriptor for your <code>@property</code> registration allows you to control that behavior for your custom property.</p> <p>If <code>true</code>, the computed value can look to an ancestor for its value if the property is not explicitly set, and if a value isn't found it will use the initial value.</p> <p>If <code>false</code>, the computed value will use the initial value if the property is not explicitly set for the element, such as via a class or element rule.</p> <p>In this demonstration, the <code>--box-bg</code> has been set to <code>inherits: false</code>, and only the outer box has an explicit definition via the applied class. The inner box uses the initial value since inheritance is not allowed.</p> <details open=""> <summary>CSS for "Result of setting inherits: false"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-bg</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> cyan<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-bg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.outer-box</span> <span class="token punctuation">{</span> <span class="token property">--box-bg</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --box-bg { syntax: "<color>"; inherits: false; initial-value: cyan; } .box-215 { background-color: var(--box-bg); aspect-ratio: 1; border-radius: 4px; padding: 1.5rem; } .outer-box-215 { --box-bg: purple; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-215 outer-box-215"> <div class="box-215"></div> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="valid-use-of-initial-value">Valid Use of <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#valid-use-of-initial-value" aria-labelledby="valid-use-of-initial-value"><span hidden="">#</span></a></div> <p>Unless your syntax is open to any value using the universal syntax definition - <code>&quot;*&quot;</code> - then it is required to set an <code>initial-value</code> to gain the benefits of registering a custom property.</p> <p>As we've already experienced, use of <code>initial-value</code> was critical in preventing the condition of a completely broken render due to <em>invalid at computed value time</em>. Here are some other benefits of using <code>@property</code> with an <code>initial-value</code>.</p> <p>When building design systems or UI libraries, it's important to ensure your custom properties are robust and reliable. Providing an <code>initial-value</code> can help prevent a broken experience. Plus, typing properties also meshes nicely with keeping the intent of design tokens which may be expressed as custom properties.</p> <p>Dynamic computation scenarios such as the use of <code>clamp()</code> have the potential to include an invalid value, whether through an error or from the browser not supporting something within the function. Having a fallback via <code>initial-value</code> ensures that your design remains functional. This fallback behavior is a safeguard for unsupported features as well, though that can be limited by whether the <code>@property</code> rule is supported in the browser being used.</p> <blockquote> <p>Review additional ways to <a href="https://moderncss.dev/how-custom-property-values-are-computed/#preventing-invalid-at-computed-value-time">prevent invalid at computed time</a> that may be more appropriate for your browser support matrix, especially for critical scenarios.</p> </blockquote> <p>Incorporating <code>@property</code> with <code>initial-value</code> not only enhances the reliability of your CSS but also opens the door to the possibility of better tooling around custom properties. We've previewed the behavior change in browser DevTools, but I'm hopeful for an expansion of tooling including IDE plugins.</p> <p>The added layer of security from using <code>@property</code> with <code>initial-value</code> helps maintain the intent of your design, even if it isn't perfect for every context.</p> <div class="heading-wrapper h2"> <h2 id="limitations-of-initial-value">Limitations of <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#limitations-of-initial-value" aria-labelledby="limitations-of-initial-value"><span hidden="">#</span></a></div> <p>The <code>initial-value</code> is subject to the <code>syntax</code> you define for <code>@property</code>. Beyond that, <code>syntax</code> itself doesn't support every possible value combination, which we previously covered. So, sometimes a little creativity is needed to get the benefit.</p> <p>Also, <code>initial-value</code> values must be <a href="https://www.w3.org/TR/css-properties-values-api-1/#the-registerproperty-function">what the spec calls <em>computationally independent</em></a>. Simplified, this means relative values like <code>em</code> or dynamic functions like <code>clamp()</code> or <code>light-dark()</code> are unfortunately not allowed. However, in these scenarios you can still set an acceptable initial value, and then use a relative or dynamic value when you <em>use</em> the custom property, such as in the <code>:root</code> assignment.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --heading-font-size</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--heading-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> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This limitation on relative units or dynamic functions also means other custom properties cannot be used in the <code>initial-value</code> assignment. The previous technique can still be used to mitigate this, where the preferred outcome is composed in the use of the property.</p> <p>Finally, custom properties registered via <code>@property</code> are still locked into the rules of regular properties, such as that they cannot be used to enable variables in media or container query at-rules. For example, <code>@media (min-width: var(--mq-md))</code> would still be invalid.</p> <div class="heading-wrapper h2"> <h2 id="unsupported-initial-value-can-crash-the-page">Unsupported <code>initial-value</code> Can Crash the Page</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#unsupported-initial-value-can-crash-the-page" aria-labelledby="unsupported-initial-value-can-crash-the-page"><span hidden="">#</span></a></div> <p>As of time of writing, using a property or function value that a browser may not support as part of the <code>initial-value</code> definition can cause the entire page to crash!</p> <p>Fortunately, we can use <code>@supports</code> to test for ultra-modern properties or features before we try to use them as the <code>initial-value</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span>[property|feature]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Feature is supported, use for initial-value */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span>[property|feature]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Feature unsupported, use alternate for initial-value */</span> <span class="token punctuation">}</span></code></pre> <p>There may still be some surprises where <code>@supports</code> reports true, but testing will reveal a crash or other error (ex. <code>currentColor</code> used with <code>color-mix()</code> in Safari). Be sure to test your solutions cross-browser!</p> <p>Learn more about ways to <a href="https://moderncss.dev/testing-feature-support-for-modern-css/">test feature support for modern CSS</a>.</p> <div class="heading-wrapper h2"> <h2 id="exceptions-to-dynamic-limitations">Exceptions to Dynamic Limitations</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#exceptions-to-dynamic-limitations" aria-labelledby="exceptions-to-dynamic-limitations"><span hidden="">#</span></a></div> <p>There are a few conditions which may feel like exceptions to the requirement of &quot;computationally independent&quot; values when used for the <code>initial-value</code>.</p> <p>First, <code>currentColor</code> is accepted. Unlike a relative value such as <code>em</code> which requires computing <code>font-size</code> of ancestors to compute itself, the value of <code>currentColor</code> can be computed without depending on context.</p> <details open=""> <summary>CSS for "Use of currentColor as initial-value"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --border-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 3px solid <span class="token function">var</span><span class="token punctuation">(</span>--border-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --border-color { syntax: "<color>"; inherits: false; initial-value: currentColor; } .demo-553 h2 { color: blue; border: 3px solid var(--border-color); padding: 1em; } .demo-553 code { color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="demo-553"> <h2>My border is set to <code>currentColor</code></h2> </div> </div> </div> <p>Second, use of <code>&quot;&lt;length-percentage&gt;&quot;</code> enables the use of <code>calc()</code>, which is mentioned in the spec. This allows a calculation that includes what is considered a global, computationally independent unit set even though we often use them for dynamic behavior. That is, the use of viewport units.</p> <p>For a scenario such as fluid type, this provides a better fallback that keeps the spirit of the intended outcome even though it's overall less ideal for most scenarios.</p> <details open=""> <summary>CSS for "Use of calc() with vi for initial-value"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --heading-font-size</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length-percentage>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>18px + 1.5vi<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* In practice, define your ideal sizing function using `clamp()` via an assignment on `:root` */</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--heading-font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --heading-font-size { syntax: "<length-percentage>"; inherits: true; initial-value: calc(18px + 1.5vi); } .demo-996 h2 { font-size: var(--heading-font-size); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="demo-996"> <h2>Resize the window to see the fluid behavior</h2> </div> </div> </div> <p><em>Note</em>: While we typically recommend using <code>rem</code> for <code>font-size</code> definitions, it is considered a relative value and not accepted for use in <code>initial-value</code>, hence the use of <code>px</code> in the calculation.</p> <div class="heading-wrapper h2"> <h2 id="consequences-of-setting-initial-value">Consequences of Setting <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#consequences-of-setting-initial-value" aria-labelledby="consequences-of-setting-initial-value"><span hidden="">#</span></a></div> <p>In some scenarios, registering a property without the universal syntax - which means an <code>initial-value</code> is required - has consequences, and limits the property's use.</p> <p>Some reasons for preferring optional component properties include:</p> <ul> <li>to use the regular custom property fallback method for your default value, especially if the fallback should be another custom property (ex. a design token)</li> <li>an <code>initial-value</code> may result in an unwanted default condition, particularly since it can't include another custom property</li> </ul> <p>A technique I love to use for flexible component styles is including an intentionally undefined custom property so that variants can efficiently be created just by updating the custom property value. Or, purposely using entirely undefined properties to make the base class more inclusive of various scenarios by treating custom properties like a component style API.</p> <p>For example, if I registered <code>--button-background</code> here as a color, it would never use the correct fallback when my intention was for the default variant to use the fallback.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.button</span> <span class="token punctuation">{</span> <span class="token comment">/* Use of initial-value would prevent ever using the fallback */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-background<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Intended to be undefined and therefore considered invalid until set */</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-border-radius<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button--secondary</span> <span class="token punctuation">{</span> <span class="token property">--button-background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-secondary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button--rounded</span> <span class="token punctuation">{</span> <span class="token property">--button-border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>If you also have these scenarios, you may consider using a mixed approach of typing your primitive properties - like <code>--color-primary</code> - but not the component-specific properties.</p> <div class="heading-wrapper h2"> <h2 id="considerations-for-using-property">Considerations For Using <code>@property</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#considerations-for-using-property" aria-labelledby="considerations-for-using-property"><span hidden="">#</span></a></div> <p>While some of the demos in this article intentionally were set up to have the rendered output use <em>only</em> the <code>initial-value</code>, in practice it would be best to separately define the custom property. Again, this is presently a new feature, so without an additional definition such as in <code>:root</code> you risk not having a value at all if you swap to only relying on <code>initial-value</code>.</p> <p>You should also be aware that it is possible to register the same property multiple times, and that cascade rules mean the last one will win. This raises the potential for conflicts from accidental overrides. There isn't a way to &quot;scope&quot; the <code>@property</code> rule within a selector.</p> <p>However, use of cascade layers can modify this behavior since unlayered styles win over layered styles, which includes at-rules. Cascade layers might be a way to manage registration of <code>@property</code> rules if you assign a &quot;properties&quot; layer early on and commit to assigning all registrations to that layer.</p> <p>Custom properties can also be <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS/registerProperty_static">registered via JavaScript</a>. In fact, this was the original way to do it since this capability was originally coupled with the Houdini APIs. If a property is registered via JS, that definition is likely to win over the one in your stylesheets. That said, if your actual intent is to change a custom property value via JS, learn the more appropriate way to <a href="https://12daysofweb.dev/2021/css-custom-properties/#accessing-and-setting-custom-properties-with-javascript">access and set custom properties with JS</a>.</p> <p>Use of <code>@property</code> has the potential for strengthening container style queries, especially if you are registering properties to act as toggles or enums. In this example, the use of <code>@property</code> helps by typing our theme values, and ensures a fallback of &quot;light&quot;.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --theme</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"light | dark"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--theme</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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">--theme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><em>Learn more about this particular idea of <a href="https://thinkdobecreate.com/articles/simplified-dark-mode-with-style-queries/">using style queries for simplified dark mode</a></em>.</p> <p>Although it's a bit outside the scope of this article, another benefit of typing custom properties is that they become animatable. This is because the type turns the value into something CSS knows how to work with, vs. the mysterious open-ended value it would otherwise be. Here's a CodePen example of how registering a color custom property allows <a href="https://codepen.io/5t3ph/pen/LYgWQgL">animating a range of colors</a> for the background.</p> <hr /> <p>Use of <code>@property</code> enables writing safer CSS custom properties, which improves the reliability of your system design, and defends against errors that could impact user experience. A reminder that for now they are a progressive enhancement and should almost always be used in conjunction with an explicit definition of the property.</p> <p>Be sure to test to ensure your intended outcome of both the allowed syntax, and the result if the <code>initial-value</code> is used in the final render.</p> 12 Modern CSS One-Line Upgrades 2024-01-19T00:00:00Z https://moderncss.dev/12-modern-css-one-line-upgrades/ <p>Sometimes, improving your application CSS just takes a one-line upgrade or enhancement! Learn about 12 properties to start incorporating into your projects, and enjoy reducing technical debt, removing JavaScript, and scoring easy wins for user experience.</p> <p>Properties are explored for the following categories:</p> <ul> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-upgrades">Stable Upgrades</a></strong>: fix a hack or issue by replacing older techniques</li> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-enhancements">Stable Enhancements</a></strong>: provide an improved experience with well-supported modern properties</li> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#progressive-enhancements">Progressive Enhancements</a></strong>: provide an upgraded experience when these properties are supported without causing harm in unsupporting browsers</li> </ul> <div class="heading-wrapper h2"> <h2 id="stable-upgrades"><strong>Stable Upgrades</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-upgrades" aria-labelledby="stable-upgrades"><span hidden="">#</span></a></div> <p>The following well-supported properties can help fix a hack or long-standing issue by replacing older techniques.</p> <div class="heading-wrapper h3"> <h3 id="aspect-ratio">aspect-ratio</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#aspect-ratio" aria-labelledby="aspect-ratio"><span hidden="">#</span></a></div> <p>Have you ever used the “<a href="https://css-tricks.com/aspect-ratio-boxes/">padding hack</a>” to force an aspect ratio such as 16:9 for video embeds? As of September 2021, the <code>aspect-ratio</code> property is stable in evergreen browsers and is the only property needed to define an aspect ratio.</p> <p>For an HD video, you can just use <code>aspect-ratio: 16/9</code>. For a perfect square, only <code>aspect-ratio: 1</code> is required since the implied second value is also <code>1 </code>.</p> <details open=""> <summary>CSS for "Basic use of aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.aspect-ratio-hd</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 16/9<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.aspect-ratio-square</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .aspect-ratio-hd-258 { aspect-ratio: 16/9; } .aspect-ratio-square-258 { aspect-ratio: 1; } :is(.aspect-ratio-hd-258, .aspect-ratio-square-258) { background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); display: grid; place-content: center; padding: 1em; } :is(.aspect-ratio-hd-258, .aspect-ratio-square-258):not(:first-child) { margin-top: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <p class="aspect-ratio-hd-258"> HD 16:9 </p> <p class="aspect-ratio-square-258"> Square 1:1 </p> </div> </div> <p>Of note, an applied <code>aspect-ratio</code> is forgiving and will allow content to take precedence. This means that when content would cause the element to exceed the ratio in at least one dimension, the element will still grow or change shape to accommodate the content. To prevent or control this behavior, you can add additional dimension properties, like <code>max-width</code>, which may be necessary to avoid expanding out of a flex or grid container.</p> <details open=""> <summary>CSS for "Forgiving aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token comment">/* Applied to the flexbox children which have a size constraint from their parent */</span> <span class="token selector">.aspect-ratio-square</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .aspect-ratio-square-88 { aspect-ratio: 1; background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); display: grid; align-items: center; padding: 1em; } .container-88 { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-start; margin-inline: auto; max-width: 65ch; } .container-88 > * { flex: 1 1 20ch; } </style> <div class="demo"> <div class="demo--content demo--place-center demo--padding"> <div class="container-88"> <p class="aspect-ratio-square-88"> Lorem, ipsum dolor sit amet consectetur adipisicing elit. Iste molestias maiores velit quaerat debitis incidunt delectus nulla, quibusdam fugit eos? Fugiat asperiores assumenda nulla corrupti, sit repellendus ducimus necessitatibus voluptates. </p> <p class="aspect-ratio-square-88"> Lorem, ipsum dolor sit amet consectetur adipisicing elit. </p> <p class="aspect-ratio-square-88"> Iste molestias maiores velit quaerat debitis incidunt delectus nulla, quibusdam fugit eos? </p> </div> </div> </div> <p>If you are hesitant to fully replace the padding hack and still want to provide some dimension guardrails, review the <a href="https://smolcss.dev/#smol-aspect-ratio-gallery">Smol Aspect Ratio Gallery</a> for a progressively enhanced solution to <code>aspect-ratio</code>.</p> <div class="heading-wrapper h3"> <h3 id="object-fit">object-fit</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#object-fit" aria-labelledby="object-fit"><span hidden="">#</span></a></div> <p>This is actually the oldest property in this list, but it solves an important issue and definitely fits the sentiment of a one-line upgrade.</p> <p>The use of <code>object-fit</code> causes an <code>img</code> or other <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element">replaced element</a> to act as the container for its contents and have the those contents adopt resizing behavior similar to <code>background-size</code>.</p> <p>While there are a few values available for <code>object-fit</code>, the following are the ones you’re most likely to use:</p> <ul> <li><code>cover</code> - the image resizes to <em>cover</em> the element, and maintains its aspect-ratio so that the content is not distorted</li> <li><code>scale-down</code> - the image resizes (if needed) <em>within</em> the element so that it is fully visible without being clipped and maintains its aspect-ratio, which may lead to extra space (”letterboxing”) around the image if the element has a different rendered aspect-ratio</li> </ul> <p>In either case, <code>object-fit</code> is an excellent property pairing with <code>aspect-ratio</code> to ensure images are not distorted when you apply a custom aspect ratio.</p> <details open=""> <summary>CSS for "Use of object-fit with aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.image</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token comment">/* Optional: constrain image size */</span> <span class="token property">max-block-size</span><span class="token punctuation">:</span> 250px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .image-373 { object-fit: cover; aspect-ratio: 1; /* Optional: constrain image size */ max-block-size: 250px; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <img src="https://moderncss.dev/img/posts/photo.jpg" alt="A closeup of a siamese cat peeking over an object only their head visible from the eyes up to their ears." class="image-373" /> </div> </div> <blockquote> <p>Review my <a href="https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit?af=2s65ms">explanation of object-fit</a> in this free egghead video lesson.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="margin-inline">margin-inline</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#margin-inline" aria-labelledby="margin-inline"><span hidden="">#</span></a></div> <p>One of many logical properties, <code>margin-inline</code> functions as a shorthand for setting the inline (left and right in horizontal writing modes) margin.</p> <p>The replacement here is simple:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Before */</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token comment">/* After */</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></code></pre> <p>Logical properties have been available for a couple of years and now have <a href="https://caniuse.com/css-logical-props">support upwards of 98%</a> (with occasional prefixing). Review this article from Ahmad Shadeed to learn more about <a href="https://ishadeed.com/article/css-logical-properties/">using logical properties</a> and their importance for sites with international audiences.</p> <div class="heading-wrapper h2"> <h2 id="stable-enhancements"><strong>Stable Enhancements</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-enhancements" aria-labelledby="stable-enhancements"><span hidden="">#</span></a></div> <p>These well-supported modern CSS properties can provide an improved experience, and may also allow replacing older methods or even JavaScript-aided solutions. Fallback solutions are not likely to be needed, although this is dependent on your specific application considerations, and testing is always encouraged.</p> <div class="heading-wrapper h3"> <h3 id="text-underline-offset">text-underline-offset</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#text-underline-offset" aria-labelledby="text-underline-offset"><span hidden="">#</span></a></div> <p>The use of <code>text-underline-offset</code> allows you to control the distance between the text baseline and the underline. This property has become a part of my standard reset, applied as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">text-underline-offset</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can use this offset to clear descenders as well as (subjectively) improve legibility, particularly when links are grouped in close proximity, such as a bulleted list of links.</p> <p>This upgrade may replace older hacks like a border or pseudo-element, or even a gradient background, especially when used with its friends:</p> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color">text-decoration-color</a> to change the underline color</li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-thickness">text-decoration-thickness</a> to change the underline stroke thickness.</li> </ul> <div class="heading-wrapper h3"> <h3 id="outline-offset">outline-offset</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#outline-offset" aria-labelledby="outline-offset"><span hidden="">#</span></a></div> <p>Have you been using <code>box-shadow</code> or perhaps a pseudo-element to supply a custom outline when you wanted distance between the element and outline on focus?</p> <p>Good news! The long-available <code>outline-offset</code> property (<a href="https://caniuse.com/?search=outline-offset">as early as 2006</a>!) may be one you missed, and it enables pushing the outline away from the element with a positive value or pulling it into the element with a negative value.</p> <p>In the demo, the gray solid line is the element border, and the blue dashed line is the outline being positioned via <code>outline-offset</code>.</p> <details open=""> <summary>CSS for "Positive and negative outline-offset"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.outline-offset</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px dashed blue<span class="token punctuation">;</span> <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> .5em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-488 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; gap: 2rem; } .outline-offset-488 { outline: 2px dashed blue; outline-offset: var(--outline-offset, 0.5em); padding: 1em; border: 1px solid gray; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <div class="container-488"> <span class="outline-offset-488">Positive offset</span> <span class="outline-offset-488" style="--outline-offset: -0.5em;">Negative offset</span> </div> </div> </div> <p><strong>Reminder</strong>: outlines are <em>not</em> computed as part of the element’s box size, so increasing the distance will not increase the amount of space an element occupies. This is similar to how <code>box-shadow</code> is rendered without impacting the element size as well.</p> <blockquote> <p>Learn more about <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility">using outline-offset as an accessibility improvement</a> for focus visibility.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="scroll-margin-topbottom">scroll-margin-top/bottom</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#scroll-margin-topbottom" aria-labelledby="scroll-margin-topbottom"><span hidden="">#</span></a></div> <p>The <code>scroll-margin</code> set of properties (and corresponding <code>scroll-padding</code>) allows adding an offset to an element in the context of the scroll position. In other words, adding <code>scroll-padding-top</code> can increase scroll offset above the element but doesn’t affect its layout position within the document.</p> <p>Why is this useful? Well, it can alleviate issues caused by a sticky nav element covering content when an anchor link is activated. Using <code>scroll-margin-top</code> we can increase the space above the element when it is scrolled to via navigation to account for the space occupied by the sticky nav.</p> <p>I like to include a generic starting rule in my reset for any element with an <code>[id]</code> attribute given it has the potential to become an anchor link.</p> <pre class="language-css"><code class="language-css"><span class="token selector">[id]</span> <span class="token punctuation">{</span> <span class="token property">scroll-margin-top</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>An alternative selector is explored in the Modern CSS article on <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-reset-additions">component-based architecture</a> and is also in use on this site, as can be tested by using the links from the article table of contents sidebar.</p> <p>For a more robust solution when accounting for the overlap of sticky, fixed, or absolute positioned elements, you may want to use a custom property with a fallback value. Then, with the assistance of JavaScript, measure the real distance needed and <a href="https://12daysofweb.dev/2021/css-custom-properties/#accessing-and-setting-custom-properties-with-javascript">update the custom property value</a>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">[id]</span> <span class="token punctuation">{</span> <span class="token comment">/* Update --scroll-margin with JS if needed */</span> <span class="token property">scroll-margin-top</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--scroll-margin<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>I encourage you to also update this solution further and use the logical property equivalents: <code>scroll-padding-block-start</code> and <code>-block-end</code>.</p> <div class="heading-wrapper h3"> <h3 id="color-scheme">color-scheme</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#color-scheme" aria-labelledby="color-scheme"><span hidden="">#</span></a></div> <p>You may be familiar with the <code>prefers-color-scheme</code> media query to customize dark and light themes. The CSS property <code>color-scheme</code> is an opt-in to adapting browser UI elements including form controls, scrollbars, and CSS system colors. The adaptation asks the browser to render those items with either a <code>light</code> or <code>dark</code> scheme, and the property allows defining a preference order.</p> <p>If you’re enabling adapting your entire application, set the following on the <code>:root</code>, which says to preference a <code>dark</code> theme (or flip the order to preference a <code>light</code> theme).</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> dark light<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can also define <code>color-scheme</code> on individual elements, such as adjusting form controls within an element with a dark background for improved contrast.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.dark-background</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Learn from Sara Joy’s presentation about how to <a href="https://www.youtube.com/watch?v=Lye56NHGtLA">use color-scheme for easy dark mode</a>, and more about incorporating this feature.</p> <div class="heading-wrapper h3"> <h3 id="accent-color">accent-color</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#accent-color" aria-labelledby="accent-color"><span hidden="">#</span></a></div> <p>If you’ve ever wanted to change the color of checkboxes or radio buttons, you’ve been seeking <code>accent-color</code>. With this property, you can modify the <code>:checked</code> appearance of radio buttons and checkboxes and the filled-in state for both the progress element and range input. The browser’s default focus “halo” may also be adjusted if you do not have another override.</p> <details open=""> <summary>CSS for "Effect of using accent-color"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">accent-color</span><span class="token punctuation">:</span> mediumvioletred<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-129 { display: grid; gap: 1rem; accent-color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <div class="container-129"> <label> <input type="radio" checked="" /> Radio </label> <label> <input type="checkbox" checked="" /> Checkbox </label> <label> Range<br /> <input type="range" min="0" max="100" value="30" /> </label> <label> Progress<br /> <progress max="100" value="70">70%</progress> </label> </div> </div> </div> <p>Consider adding both <code>accent-color</code> and <code>color-scheme</code> to your baseline application styles for a quick win toward custom theme management.</p> <blockquote> <p>If you need more comprehensive custom styling for form controls, review the Modern CSS series beginning with <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="width-fit-content">width: fit-content</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#width-fit-content" aria-labelledby="width-fit-content"><span hidden="">#</span></a></div> <p>One of my favorite CSS hidden gems is the use of <code>fit-content</code> to “shrink wrap” an element to its contents.</p> <p>Whereas you may have used an inline display value such as <code>display: inline-block</code> to reduce an element’s width to the content size, an upgrade to <code>width: fit-content</code> will achieve the same effect. The advantage of <code>width: fit-content</code> is that it leaves the <code>display</code> value available, thereby not changing the position of the element in the layout unless you adjust that as well. The computed box size will adjust to the dimensions created by <code>fit-content</code>.</p> <details open=""> <summary>CSS for "Basic usage of fit-content"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fit-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fit-content-135 { width: fit-content; padding: 0.5em; border-radius: 0.25rem; background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); } .regular-135 { background-color: lightgray; margin-top: 1em; padding: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="fit-content-135">Using fit-content</p> <p class="regular-135">Without the use of fit-content</p> </div> </div> <p>The <code>fit-content</code> value is one of several <a href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/">keywords that enable intrinsic sizing</a>.</p> <p>Consider the secondary upgrade for this technique to the logical property equivalent of <code>inline-size: fit-content</code>.</p> <div class="heading-wrapper h2"> <h2 id="progressive-enhancements"><strong>Progressive Enhancements</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#progressive-enhancements" aria-labelledby="progressive-enhancements"><span hidden="">#</span></a></div> <p>This last set of properties provides an upgraded experience when they are supported and can be used without fear of causing harm in unsupporting browsers. This means they do not need a fallback method even though they are more recent additions to modern CSS.</p> <div class="heading-wrapper h3"> <h3 id="overscroll-behavior">overscroll-behavior</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#overscroll-behavior" aria-labelledby="overscroll-behavior"><span hidden="">#</span></a></div> <p>The default behavior of contained scroll regions - areas with limited dimensions where overflow is allowed to be scrolled - is that when the scroll runs out in the element, the scroll interaction passes to the background page. This can be jarring at best, and frustrating at worst for your users.</p> <p>Use of <code>overscroll-behavior: contain</code> will isolate the scrolling to the contained region, preventing continuing the scroll by moving it to the parent page once the scroll boundary is reached. This is useful in contexts such as a sidebar of navigation links, which may have an independent scroll from the main page content, which may be a long article or documentation page.</p> <details open=""> <summary>CSS for "Basic usage of overscroll-behavior"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar, .article</span> <span class="token punctuation">{</span> <span class="token property">overscroll-behavior</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-736 { display: grid; grid-template-columns: fit-content(25ch) minmax(50%, 1fr); } .sidebar-736, .article-736 { display: grid; gap: 1em; padding: 1rem; height: max(40vh, 300px); overflow-y: auto; } .sidebar-736, .article-736 { overscroll-behavior: contain; } .sidebar-736 a { color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <div class="container-736"> <div class="sidebar-736"> <a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 1</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 2</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 3</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 4</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 5</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 6</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 7</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 8</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 9</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 10</a> </div> <div class="article-736"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime nobis consectetur earum!</p> <p>Aliquid a praesentium quis in consequuntur mollitia laboriosam illum nemo commodi aut?</p> <p>Doloremque sapiente quos dignissimos sequi cupiditate commodi nemo non perspiciatis placeat totam.</p> <p>Reiciendis, at nulla! Hic nemo eius atque laborum consequuntur iusto exercitationem quasi.</p> <p>Est, assumenda amet culpa veritatis maxime debitis? Suscipit error amet quas sed?</p> <p>Magnam exercitationem neque error deleniti consequuntur, dolor repellat quo perferendis dicta sunt.</p> <p>Ipsum id repellat velit laudantium vel autem eos non aperiam qui nobis?</p> <p>Ratione maxime neque numquam minima, omnis dolorem temporibus laboriosam dolor atque suscipit?</p> <p>Dolores assumenda dolore similique eaque, odio voluptatibus. Aliquid iusto nostrum iste? Exercitationem.</p> <p>Quos adipisci ea ullam amet blanditiis voluptatibus, fuga laborum sed facere quasi!</p> <p>Odio rerum labore veritatis esse eaque quae debitis possimus ea omnis vitae.</p> <p>Quasi eum obcaecati laborum eaque nostrum numquam tempore reprehenderit qui beatae debitis?</p> <p>Optio atque fugit quia reprehenderit dolor rem et delectus praesentium ratione provident.</p> <p>Sed accusamus at architecto dolore minima error, assumenda amet nam perferendis odit?</p> <p>Et eius enim est hic doloribus reiciendis qui cupiditate? Autem, iure cupiditate?</p> </div> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="text-wrap">text-wrap</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#text-wrap" aria-labelledby="text-wrap"><span hidden="">#</span></a></div> <p>One of the newest properties (as of 2023) is <code>text-wrap</code>, which has two values that solve the type-setting problem of unbalanced lines. This includes preventing “orphans,” which describes a lonely word sitting by itself in the last text line.</p> <p>The first available value is <code>balance</code>, which has a goal of evening out the number of characters per line of text.</p> <p>There is a limitation of six lines of wrapped text, so the technique is best used on headlines or other shorter text passages. Limiting the scope of application also helps limit the impact on page rendering speed.</p> <details open=""> <summary>CSS for "Applying text-wrap: balance"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:is(h1, h2, h3, h4, .text-balance)</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token comment">/* For demonstration */</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 25ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .text-balance-888 { text-wrap: balance; } .demo-text-888 { max-inline-size: 25ch; padding: 0.25em; border: 1px dashed gray; font-family: "Baloo 2", system-ui; overflow: auto; resize: horizontal; } .demo-text-888:not(:first-child) { margin-block-start: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="demo-text-888 text-balance-888">This text has been balanced by text-wrap</p> <p class="demo-text-888">This text has not been balanced by text-wrap</p> </div> </div> <p>The other value of <code>pretty</code> specifically addresses preventing orphans and can be more broadly applied. The algorithm behind <code>pretty</code> will evaluate the last four lines in a text block to work out adjustments as needed to ensure the last line has two or more words.</p> <details open=""> <summary>CSS for "Applying text-wrap: balance"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> pretty<span class="token punctuation">;</span> <span class="token comment">/* For demonstration */</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 35ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .text-balance-114 { text-wrap: pretty; } .text-balance-114, .non-text-balance-114 { max-inline-size: 45ch; padding: 0.25em; border: 1px dashed gray; overflow: auto; resize: horizontal; } .demo-text-114 { font-family: "Baloo 2", system-ui; } .demo-text-114:not(:first-child) { margin-block-start: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="demo-text-114"><strong>With text-wrap: pretty</strong></p> <p class="demo-text-114 text-balance-114">Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore cupiditate aliquid, facere explicabo voluptatibus iure! Saepe nostrum quasi corporis totam accusamus beatae obcaecati rerum fugiat minima perferendis voluptatibus.</p> <p class="demo-text-114"><strong>Without</strong></p> <p class="demo-text-114 non-text-balance-114">Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore cupiditate aliquid, facere explicabo voluptatibus iure! Saepe nostrum quasi corporis totam accusamus beatae obcaecati rerum fugiat minima perferendis voluptatibus.</p> </div> </div> <p>Use of <code>text-wrap</code> is a great progressive enhancement. However, any adjustments do 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> <div class="heading-wrapper h3"> <h3 id="scrollbar-gutter">scrollbar-gutter</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#scrollbar-gutter" aria-labelledby="scrollbar-gutter"><span hidden="">#</span></a></div> <p>In some scenarios, the appearance or disappearance of scrollbars can cause an unwanted layout shift. For example, when a dialog overlay is displayed and the background page adds <code>overflow: hidden</code> to prevent scrolling, causing a shift from removing the no longer needed scrollbars.</p> <p>The modern CSS property <code>scrollbar-gutter</code> enables a reservation of space for scrollbars in the layout, which prevents that undesirable shift. When there’s no need for a scrollbar, the browser will still paint a gutter as extra space created in addition to any padding on the scroll container.</p> <blockquote> <p><strong>Important</strong>: This property only has an effect when the user’s system settings are <em>not</em> for “overlay” scrollbars, as in the default for Mac OS, where the scrollbar only appears as an overlay on content that is actively being scrolled. Do not drop padding in favor of <code>scrollbar-gutter</code> since you will lose all intended space when overlay scrollbars are in use.</p> </blockquote> <p>Since this is visually apparent extra space, you may choose to assign this property using two keywords: <code>scrollbar-gutter: stable both-edges</code>. The use of <code>stable</code> by itself only adds a gutter where the scrollbar would otherwise be, while the addition of <code>both-edges</code> preferences adding the space to the opposite side of the scroll container, too. This ensures visual balance when the layout doesn’t yet need scrollbars to be visible. See <a href="https://defensivecss.dev/tip/scrollbar-gutter/">a visual of using scrollbar-gutter</a> from Ahmad Shadeed.</p> <hr /> <p>Be sure to review more of the articles here to upgrade your CSS knowledge even more! A great place to start is <a href="https://moderncss.dev/topics/">the topics list.</a></p> How Custom Property Values are Computed 2023-09-14T00:00:00Z https://moderncss.dev/how-custom-property-values-are-computed/ <p>Custom properties - aka “CSS variables” - seem fairly straightforward. However, there are some behaviors to be aware of regarding how the browser computes the final values. A misunderstanding of this process may lead to an unexpected or missing value and difficulty troubleshooting and resolving the issue.</p> <p>To help you use custom properties confidently and troubleshoot efficiently, we’ll review:</p> <ul> <li>how the browser determines values for any property</li> <li>the impact of “computed value time”</li> <li>pitfalls around using custom properties with cutting-edge CSS</li> <li>why inheritance should inform your custom property architecture</li> <li>strategies to prevent invalid computed values</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="computed-inherited-and-initial-values">Computed, Inherited, and Initial Values</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#computed-inherited-and-initial-values" aria-labelledby="computed-inherited-and-initial-values"><span hidden="">#</span></a></div> <p>When the browser parses CSS, its goal is to calculate one value per property per element in the DOM.</p> <p>Something you learn early on about CSS is that you can change a property’s value multiple times from multiple rules that may select the same element.</p> <p>Given the HTML <code>&lt;h2 class=&quot;card__title&quot;&gt;</code>, all of the following are eligible matches for the <code>color</code> property.</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #222<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #74e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__title</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #93b<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Each of these are <em>declared</em> values, and due to specificity and the cascading order, the element’s final selected value may be the <em>cascaded</em> value. In this case, <code>.card__title</code> wins for the <code>color</code> property.</p> <p>If a property does not receive a value from the cascade, then it will use either the <em>inherited</em> or <em>initial</em> value.</p> <ul> <li><em>Inherited</em> values come from the nearest ancestor that has assigned a value, if the property is <a href="https://web.dev/learn/css/inheritance/#which-properties-are-inheritable">allowed to inherit</a> (ex. <code>color</code>, font properties, <code>text-align</code>)</li> <li><em>Initial</em> values are used when no inherited value exists or is allowed and are the values provided by the specification for the property</li> </ul> <p>So, for <code>&lt;h2 class=&quot;card__title&quot;&gt;</code>, the full set of values populated for the element may be as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__title</span> <span class="token punctuation">{</span> <span class="token comment">/* Cascaded value */</span> <span class="token property">color</span><span class="token punctuation">:</span> #93b<span class="token punctuation">;</span> <span class="token comment">/* Initial properties and values */</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token comment">/* Inherited properties and values */</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Source Code Pro<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> 500<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Some property definitions require further computation to absolutize the values. The following are a few of the value transforms that may occur.</p> <ol> <li>Relative units such as <code>vw</code>, <code>em</code>, and <code>%</code> are converted to pixel values, and floats may be converted to integers</li> <li><code>currentColor</code> and named colors like <code>rebeccapurple</code> are converted to an <code>sRGB</code> value</li> <li>Compositing values that affect each other <ol> <li>ex. <code>padding: 1em</code> requires computing the value for <code>font-size</code> that <code>em</code> depends on</li> </ol> </li> <li>custom properties are replaced with their computed values</li> </ol> <p>These transformations result in the <em>computed,</em> <em>used</em>, and <strong><strong><strong>actual</strong></strong></strong> values - which refer to the progressive steps that may be involved to end up with an absolutized value. You can dive deeper into the specifics of <a href="https://www.w3.org/TR/css-cascade-4/#value-stages">how values are calculated</a> or check out this <a href="https://www.matuzo.at/blog/2023/100daysof-day82/">review of value processing</a>.</p> <div class="heading-wrapper h2"> <h2 id="custom-properties-and-computed-value-time">Custom Properties and Computed Value Time</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#custom-properties-and-computed-value-time" aria-labelledby="custom-properties-and-computed-value-time"><span hidden="">#</span></a></div> <p>One special computation scenario with a critical impact on modern CSS is when the browser assigns values to custom properties, referred to as “computed value time” (CVT).</p> <div class="heading-wrapper h3"> <h3 id="invalid-at-computed-value-time">Invalid at Computed Value Time</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#invalid-at-computed-value-time" aria-labelledby="invalid-at-computed-value-time"><span hidden="">#</span></a></div> <p>As described earlier, typically unfilled, or invalid property assignments will fall back to cascaded values when applicable.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Used due to the cascade */</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue <span class="token punctuation">}</span> <span class="token comment">/* Invalid as a "color", thrown out by the browser */</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Take a moment to see if you can determine what the <code>color</code> value of <code>.card p</code> will be in the following example.</p> <pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>.card p</code> will be the <em>inherited</em> <code>color</code> value of <code>red</code> as provided by the <code>body</code>. It is unable to use the cascaded value of <code>blue</code> due to the browser discarding that as a possible value candidate at “parse time” when it is only evaluating syntax. It is only when the user agent attempts to apply the final value - the stage of “computed value time” - that it realizes the value is invalid.</p> <p>Said another way: once the browser determines the cascaded value, which is partially based on syntactic correctness, it will trash any other candidates. For syntactically correct custom properties, the browser essentially assumes the absolutized value will succeed in being valid.</p> <p>This leads to an inability for custom properties to “fail early”. When there is a failure, the resulting value will be either an inherited value from an ancestor or the initial value for the property. (<em>If this sounds familiar, it’s because it’s also <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/unset">the behavior when using <code>unset</code></a>.</em>)</p> <p>Critically, this means <strong>an invalid custom property value is unable to fall back</strong> to a previously set cascaded value, as you may expect, because those have been discarded from the decision tree.</p> <p>All hope is not lost! If later a utility class on the paragraph were to update the <code>color</code> property, then due to rules of the cascade and specificity it would win out like normal and the invalid custom property value wouldn’t have an effect.</p> <pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Not used */</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Wins! */</span> <span class="token selector">.card .callout</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple <span class="token punctuation">}</span></code></pre> <p>Note that when referring to invalid values for custom properties that what makes it invalid is how the value is applied. For example, a space character is a valid custom property definition, but will be invalid when applied to a property.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Valid definition */</span> <span class="token property">--toggle</span><span class="token punctuation">:</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Invalid at computed time */</span> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--toggle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On the other hand, a custom property with a value of <code>100%</code> may be applied to <code>width</code> but not <code>color</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--length</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Valid */</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Invalid at computed time */</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="cvt-impact-on-modern-css-support">CVT Impact on Modern CSS Support</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#cvt-impact-on-modern-css-support" aria-labelledby="cvt-impact-on-modern-css-support"><span hidden="">#</span></a></div> <p>Another scenario where a custom property being invalid at computed value time may break your expectation is using the custom property as a partial value, or undefined with a fallback, especially paired with cutting-edge CSS features.</p> <p>Given the following, you may expect that when the <code>cqi</code> unit is not supported, the browser will simply use the prior <code>font-size</code> definition.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 1rem + 1.5vw<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Instead, the browser assumes it will understand the second <code>clamp()</code> definition and discards the prior <code>font-size</code> definitions for this <code>h2</code> rule. But when the browser goes to populate the custom property value and finds it doesn’t support <code>cqi</code>, it’s too late to use what was intended as the fallback definition. This means it instead uses the initial value, if there is no inheritable value from an ancestor.</p> <p>While you might think that the <em>initial</em> value would at least be a <code>font-size</code> befitting the <code>h2</code> level, the initial value for any element’s <code>font-size</code> is “<a href="https://drafts.csswg.org/css2/#valdef-font-size-medium">medium</a>” which is generally equivalent to <code>1rem</code>. This means you not only lose your intended fallback style, but also the visual hierarchy of the <code>h2</code> in browsers which do not support <code>cqi</code>.</p> <p><img src="https://moderncss.dev/img/posts/33/cvt-cqi-support.png" alt="Two type samples, where the top is in a browser that supports cqi and the font renders at a large size, whereas the bottom sample is in an unsupported browser for cqi and the font renders at the initial size of 1rem." /></p> <p>One way to discover the <code>initial</code> value for any property is to <a href="https://developer.mozilla.org/en-US/">search for it on MDN</a>, and look for the “Formal Definition” section which will list the initial value, as well as whether the value is eligible for inheritance.</p> <p>A few <code>initial</code> values to be aware of besides <code>font-size</code>:</p> <ul> <li><code>background-color</code>: <code>transparent</code></li> <li><code>border-color</code>: <code>currentColor</code></li> <li><code>border-width</code>: <code>medium</code> which equates to <code>3px</code></li> <li><code>color</code>: <code>canvastext</code> which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/system-color">a system color</a> and likely to be black, but may change due to forced-colors modes</li> <li><code>font-family</code>: depends on user agent, likely to be a serif</li> </ul> <div class="heading-wrapper h3"> <h3 id="safely-supporting-modern-css-values-in-custom-properties">Safely Supporting Modern CSS Values in Custom Properties</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#safely-supporting-modern-css-values-in-custom-properties" aria-labelledby="safely-supporting-modern-css-values-in-custom-properties"><span hidden="">#</span></a></div> <p>A safer solution is to wrap the definition using <code>cqi</code> in an <code>@supports</code> so that un-supporting browsers actually use the fallback.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 1rem + 1.5vw<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Does this mean you need to change every place you use custom properties? That all depends on your support matrix (which browsers and versions you have elected to support). For super-ultra-modern properties, especially when the initial value is undesirable, this approach may be the safest. Another example of when you may use an <code>@supports</code> condition is with new color spaces, like <code>oklch()</code>.</p> <blockquote> <p><a href="https://moderncss.dev/testing-feature-support-for-modern-css/">Learn more about CSS feature detection</a> to help you choose the right route for your project.</p> </blockquote> <p>Confusingly, given a situation like the <code>cqi</code> example, browser dev tools for the un-supporting browser may still show the failing rule as being the applied style. This is likely because the browser may still support the other parts, like <code>clamp()</code>. An incorrect appearance in dev tools can make it difficult to troubleshoot issues caused by custom properties being invalid at computed time, which is why it’s important to fundamentally understand what is happening.</p> <div class="heading-wrapper h2"> <h2 id="inheritance-and-custom-properties">Inheritance and Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#inheritance-and-custom-properties" aria-labelledby="inheritance-and-custom-properties"><span hidden="">#</span></a></div> <p>Another way computed value time affects custom property value assignment is inheritance of computed values.</p> <p>Calculation of a custom property value is performed once per element, which then makes the computed value available for inheritance. Let’s learn how that impacts your custom property architecture choices.</p> <div class="heading-wrapper h3"> <h3 id="inheritable-values-become-immutable">Inheritable Values Become Immutable</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#inheritable-values-become-immutable" aria-labelledby="inheritable-values-become-immutable"><span hidden="">#</span></a></div> <p>A common convention is batching custom properties into the <code>:root</code> selector. If one of those properties involves a calculation which includes another <code>:root</code>-level custom property, then updating the modifying property from a descendent will not update the calculation.</p> <p>As in the following example, the <code>--font-size-large</code> is calculated immediately, so updating the <code>--font-size</code> property within a descendent rule will not be able to affect the value.</p> <details open=""> <summary>CSS for "Computed values are immutable"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">--font-size-large</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>2 * <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token comment">/* The new --font-size will not update the --font-size-large calculation */</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-large<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .font-size-demo-380 { --font-size: 1rem; --font-size-large: calc(2 * var(--font-size)); } .h1-380 { --font-size: 1.25rem; /* The new --font-size will not update the --font-size-large calculation */ font-size: var(--font-size-large); } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="font-size-demo-380"> <p class="h1-380">Cake muffin toffee gingerbread ice cream</p> </div> </div> </div> <p>This is because the calculation happens as soon as the browser processes the definition against the <code>:root</code>. So the <code>:root</code> definition produces a static, computed value which is inheritable, but immutable.</p> <p>This is not to say this behavior is unique to <code>:root</code>. The key concept is that once custom property values are computed, the <em>computed value</em> is only inheritable.</p> <p>To think about it another way: within the cascade, values can be inherited by descendents, but can’t pass values back to their ancestors. Essentially this is why the computed custom property value on an ancestor element cannot be modified by a descendent element.</p> <div class="heading-wrapper h3"> <h3 id="enabling-extendable-custom-property-values">Enabling Extendable Custom Property Values</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#enabling-extendable-custom-property-values" aria-labelledby="enabling-extendable-custom-property-values"><span hidden="">#</span></a></div> <p>If we lower the custom property calculation to be applied based on classes, then the browser will be able to recalculate as part of the value processing to determine the computed value. This is because it will calculate a value for elements with the class <code>font-resize</code>, and a separate value for elements with both <code>font-resize</code> and <code>font-large</code> classes.</p> <details open=""> <summary>CSS for "Computed values are per element"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.font-resize</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-adjust<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.font-large</span> <span class="token punctuation">{</span> <span class="token comment">/* Successfully modifies the value when paired with .font-resize */</span> <span class="token property">--font-size-adjust</span><span class="token punctuation">:</span> 2.5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .font-size-demo-717 { --font-size: 1rem; } .font-resize-717 { font-size: calc(var(--font-size-adjust, 1) * var(--font-size)); } .font-large-717 { /* Successfully modifies the value when paired with .font-resize-717 */ --font-size-adjust: 2.5; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="font-size-demo-717"> <p class="font-resize-717 font-large-717">Cake muffin toffee gingerbread ice cream</p> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="preventing-invalid-at-computed-value-time">Preventing Invalid at Computed Value Time</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#preventing-invalid-at-computed-value-time" aria-labelledby="preventing-invalid-at-computed-value-time"><span hidden="">#</span></a></div> <p>A few simple strategies to avoid custom property failure include:</p> <ul> <li>Use a fallback value when defining a custom property, which is the second value that can be provided to the <code>var()</code> function, ex. <code>var(--my-property, 1px)</code></li> <li>Ensure fallback values within <code>var()</code> are of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types">the correct type</a> for the property, or <a href="https://moderncss.dev/how-custom-property-values-are-computed/#provide-a-custom-initial-value-with-property">define your own with <code>@property</code></a></li> <li>If you’re using a polyfill for a new feature, check that it resolves usage in custom properties as you expect</li> <li>Use <code>@supports</code> to ensure your intended modern CSS upgrade doesn’t break intended fallback rules in un-supporting browsers</li> </ul> <p>And as always - test your solutions in as many browsers and on as many devices as you can!</p> <div class="heading-wrapper h2"> <h2 id="provide-a-custom-intial-value-with-property">Provide a custom intial value with <code>@property</code></h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#provide-a-custom-intial-value-with-property" aria-labelledby="provide-a-custom-intial-value-with-property"><span hidden="">#</span></a></div> <p>A cross-browser feature as of the release of Firefox 128 in July 2024 is a new at-rule - <code>@property</code> - which allows defining types for your custom properties.</p> <p>Helpfully, the <code>initial-value</code> parameter enables defining your own property-specific initial value which will be used in the event the computed value would otherwise be invalid!</p> <p>Given the following definition for our <code>--color-primary</code> custom property, if the computed value was invalid, the provided <code>initial-value</code> of <code>purple</code> would be used instead.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-primary</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Learn more about how to use <code>@property</code> in the Modern CSS article &quot;<a href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/">Providing Type Definitions for CSS with @property</a>.&quot;</p> Modern CSS For Dynamic Component-Based Architecture 2023-06-09T00:00:00Z https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/ <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> <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> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="css-reset-additions">CSS Reset Additions</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-reset-additions" aria-labelledby="css-reset-additions"><span hidden="">#</span></a></div> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Baseline for default links */</span> <span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token comment">/* Relatively sized thickness and offset */</span> <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> <span class="token property">text-underline-offset</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <p>As for our reset, this means our visible focus styles will be attached to only the <code>:focus-visible</code> state.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:focus-visible</span> <span class="token punctuation">{</span> <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> <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> <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> <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> <span class="token punctuation">}</span></code></pre> <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> <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> <blockquote> <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> </blockquote> <p>The last two additions to my reset involve improving the scroll position for targeted or focused elements.</p> <p>Using <code>scroll-padding</code> properties, you can adjust the scroll position in relation to elements. The &quot;padding&quot; space does not affect the layout, just the offset of the scroll position.</p> <p>In this rule, the <code>:target</code> selector matches when an element is a target of an anchor link, also known as a &quot;document fragment.&quot; The <code>scroll-padding-block-start</code> will allow for room between the target and the top of the viewport.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Scroll margin allowance above anchor links */</span> <span class="token selector">:target</span> <span class="token punctuation">{</span> <span class="token property">scroll-padding-block-start</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The use of <code>scroll-padding-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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Scroll margin allowance below focused elements to ensure they are clearly in view */</span> <span class="token selector">:focus</span> <span class="token punctuation">{</span> <span class="token property">scroll-padding-block-end</span><span class="token punctuation">:</span> 8vh<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <div class="heading-wrapper h2"> <h2 id="project-architecture">Project Architecture</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#project-architecture" aria-labelledby="project-architecture"><span hidden="">#</span></a></div> <p>Next up are two features with the potential to strongly impact your project architecture: nesting and cascade layers.</p> <div class="heading-wrapper h3"> <h3 id="css-nesting">CSS Nesting</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-nesting" aria-labelledby="css-nesting"><span hidden="">#</span></a></div> <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> <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> <p>Any selector type is valid as a nested selector. 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.</p> <p>Selectors such as <code>:is()</code> or <code>:where()</code> are useful in nested selectors. And standard class or attribute selection is also allowed, as well as the other combinators.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp; a</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">:is(a, button)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">.button</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">+ .another-element</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp;[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/* Results in: */</span> <span class="token selector">.my-element [data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">.my-element[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>&amp;</code> also allows nested selectors for pseudo-elements and pseudo-classes.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::before</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:hover</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <div class="heading-wrapper h3"> <h3 id="css-cascade-layers">CSS Cascade Layers</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-cascade-layers" aria-labelledby="css-cascade-layers"><span hidden="">#</span></a></div> <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 &quot;C&quot; 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> <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> <p>The critical rules to understand about cascade layers are:</p> <ul> <li>the initial order of layers defines the applied priority order <ul> <li>priority increases in order</li> <li>ex. first layer has less priority than the last layer</li> </ul> </li> <li>less-nested layered styles have priority over deeper nested layer styles</li> <li>un-layered styles have the highest priority over layered styles</li> </ul> <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> <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> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> typography</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> colors</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> pink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> global</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <blockquote> <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> </blockquote> <p>As with many newer features, there is much room for experimentation, and &quot;best practices&quot; or &quot;standards of use&quot; 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> <p>Here’s a layer order I have been trying out in my own projects:</p> <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> <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> <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> <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> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="theming-and-branding">Theming and Branding</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#theming-and-branding" aria-labelledby="theming-and-branding"><span hidden="">#</span></a></div> <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> <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> <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> <p>For the remainder of the examples, we’ll be building up components and branding for our imaginary product Jaberwocky.</p> <p><img src="https://moderncss.dev/img/posts/32/brand.png" alt="" /></p> <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> <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> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Color styles */</span> <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> <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> <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> <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> <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> <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> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>You may also wish to place font sizes or other &quot;tokens&quot; 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> <p>Now that we have a brand and color palette, it's time to add the other two features.</p> <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 &quot;light&quot; 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> <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> <blockquote> <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> </blockquote> <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> <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> <span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="layout">Layout</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#layout" aria-labelledby="layout"><span hidden="">#</span></a></div> <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> <div class="heading-wrapper h3"> <h3 id="css-grid-layout">CSS Grid Layout</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-grid-layout" aria-labelledby="css-grid-layout"><span hidden="">#</span></a></div> <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> <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> <details open=""> <summary>CSS for "CSS Grid Layout"</summary> <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> <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <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> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .layout-grid-51 { display: grid; grid-template-columns: repeat( auto-fit, minmax(min(100%, 30ch), 1fr) ); gap: 2cqi; } .layout-grid-51 > span { padding: 2%; text-align: center; font-size: 2cqi; background-color: lightblue; border: 2px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="layout-grid-51"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></div> </div> </div> <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> <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> <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> <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 &quot;breakpoint&quot; 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> <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> <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--layout-grid-min</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token property">--layout-grid-gap</span><span class="token punctuation">:</span> 3vw<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <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> <span class="token punctuation">)</span><span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <div class="heading-wrapper h3"> <h3 id="css-flexbox-layout">CSS Flexbox Layout</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-flexbox-layout" aria-labelledby="css-flexbox-layout"><span hidden="">#</span></a></div> <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> <p>The flexbox grid utility shares two common features with the CSS grid utility: defining a minimum &quot;column&quot; size and the <code>gap</code> size.</p> <p>With CSS grid, the parent controls the child size. But with flexbox, the children control their sizing. Therefore, it's more safe to use a non-relative value for the column size, since using a font-relative unit like <code>ch</code> may change the outcome depending on the child's own <code>font-size</code>.</p> <p>Beyond the custom properties API, 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> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> 20rem<span class="token punctuation">;</span> <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> 3vmax<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <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> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>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> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <details open=""> <summary>CSS for "CSS Flexbox Grid Layout"</summary> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> 20rem<span class="token punctuation">;</span> <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> 3vmax<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <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> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-553 { --flex-grid-min: 20rem; --flex-grid-gap: 3vmax; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); } .flex-layout-grid-553 > * { flex: 1 1 var(--flex-grid-min); } .flex-layout-grid-553 > span { padding: 2%; text-align: center; font-size: 2cqi; background-color: lightblue; border: 2px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="flex-layout-grid-553"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="prepare-for-container-queries">Prepare for Container Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#prepare-for-container-queries" aria-labelledby="prepare-for-container-queries"><span hidden="">#</span></a></div> <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> <p>To correctly query against the size of flexbox or grid children, we can enhance our utilities to include container definitions.</p> <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> <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> <span class="token selector">:is(.layout-grid, .flex-layout-grid) > *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Later examples will demonstrate how to use features of container size queries and make use of these layout utility containers.</p> <blockquote> <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> </blockquote> <div class="heading-wrapper h2"> <h2 id="component-buttons">Component: Buttons</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-buttons" aria-labelledby="component-buttons"><span hidden="">#</span></a></div> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-buttons.png" alt="" /></p> <p>Our styles will support the following variations of a button:</p> <ul> <li>a button element</li> <li>a link element</li> <li>text plus an icon</li> <li>icon plus text</li> <li>icon-only</li> </ul> <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> <div class="heading-wrapper h3"> <h3 id="custom-property-and-component-apis">Custom Property and Component APIs</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#custom-property-and-component-apis" aria-labelledby="custom-property-and-component-apis"><span hidden="">#</span></a></div> <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> <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> <p>Then, we use the values that will signify the &quot;default&quot; 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> <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> <span class="token selector">.button</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="creating-variant-styles-with-has">Creating Variant Styles with <code>:has()</code></h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#creating-variant-styles-with-has" aria-labelledby="creating-variant-styles-with-has"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span> <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> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This variant exposes a <code>--button-padding</code> custom property, and sets an explicit <code>border-radius</code>.</p> <details open=""> <summary>CSS for "Button Component"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.button</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span> <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> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> :root { /* Color styles */ --demo-primary: hsl(265, 38%, 13%); --demo-secondary: hsl(283, 6%, 45%); --demo-tertiary: hsl(257, 15%, 91%); --demo-light: hsl(270, 100%, 99%); --demo-accent: hsl(278, 100%, 92%); --demo-accent--alt: hsl(279, 100%, 97%); --demo-accent--ui: hsl(284, 55%, 66%); } .demo-container-162 { display: grid; gap: 1rem; justify-content: start; padding: 1rem; } .inclusively-hidden-162 { clip-path: inset(50%); height: 1px; width: 1px; overflow: hidden; position: absolute; white-space: nowrap; } .btn-162 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-162:where(:has(.icon-162)) { display: flex; gap: 0.5em; align-items: center; } .btn-162:where(:has(.inclusively-hidden-162)) { border-radius: 50%; padding: 0.5em; } .btn-162:where(:not(:has(.icon-162))) { text-align: center; min-inline-size: 10ch; } .btn-162:where(:not(:has(.inclusively-hidden-162))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-container-162"> <button type="button" class="btn-162">Button</button> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/" class="btn-162">Link</a> <button type="button" class="btn-162"> Text + Icon Button <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> </button> <button type="button" class="btn-162"> <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> Icon + Text button </button> <button type="button" class="btn-162"> <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> <span class="inclusively-hidden-162">Icon only button</span> </button> </div> </div> </div> <svg hidden=""> <defs> <symbol viewBox="0 0 256 256" id="star"> <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> </symbol> </defs> </svg> <div class="heading-wrapper h3"> <h3 id="using-custom-properties-api-for-states">Using Custom Properties API for States</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#using-custom-properties-api-for-states" aria-labelledby="using-custom-properties-api-for-states"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:hover</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token selector">.button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">--outline-style</span><span class="token punctuation">:</span> dashed<span class="token punctuation">;</span> <span class="token property">--outline-offset</span><span class="token punctuation">:</span> -0.35em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="component-card">Component: Card</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-card" aria-labelledby="component-card"><span hidden="">#</span></a></div> <p><img src="https://moderncss.dev/img/posts/32/preview-cards.png" alt="" /></p> <p>For the card component, we have three variants and one state to manage:</p> <ul> <li>default, small card</li> <li>“new” style</li> <li>wide with larger text</li> <li>focus-visible state</li> </ul> <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> <p>Here are a few other details about our cards and expected usage:</p> <ul> <li>we’ll place them within our flexbox-based layout grid</li> <li>the layout grid will be within a wrapping container</li> </ul> <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> <details false=""> <summary>CSS for "Base Card Styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span> <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> <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> <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> <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> <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> <span class="token property">background-size</span><span class="token punctuation">:</span> 28px 28px<span class="token punctuation">;</span> <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> <span class="token property">padding</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <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> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">align-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon::before</span> <span class="token punctuation">{</span> <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> <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> <span class="token property">font-weight</span><span class="token punctuation">:</span> 600<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon::before, .card__number-icon img</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 2.25rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon img</span> <span class="token punctuation">{</span> <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> <span class="token property">padding</span><span class="token punctuation">:</span> 0.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token selector">.card a::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">inset</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> 400<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-953 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-953 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-953 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-953 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-953 { display: flex; justify-content: space-between; } .card__number-icon-953::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-953::before, .card__number-icon-953 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-953 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-953 a { text-decoration: none; color: var(--demo-primary); } .card-953 a::before { content: ""; position: absolute; inset: 0; } .card-953 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-953 a { font-size: inherit; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-953"> <li> <div class="card-953"> <div data-num="1" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-953">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-953"> <div data-num="2" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-953"> <div data-num="3" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-953"> <div data-num="4" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-953"> <div data-num="5" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="styling-based-on-element-presence">Styling Based on Element Presence</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#styling-based-on-element-presence" aria-labelledby="styling-based-on-element-presence"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token selector">&amp;:has(.tag)</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "&#39;New&#39; Card"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token selector">.card :is(h2, h3):has(.tag)</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-370 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-370 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-370 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-370 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-370 { display: flex; justify-content: space-between; } .card__number-icon-370::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-370::before, .card__number-icon-370 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-370 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-370 a { text-decoration: none; color: var(--demo-primary); } .card-370 a::before { content: ""; position: absolute; inset: 0; } .card-370 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-370 a { font-size: inherit; } .card-370:has(.tag-370) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-370 :is(h2, h3):has(.tag-370) { display: grid; gap: 0.25em; justify-items: start; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-370"> <li> <div class="card-370"> <div data-num="1" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-370">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-370"> <div data-num="2" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-370"> <div data-num="3" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-370"> <div data-num="4" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-370"> <div data-num="5" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="special-focus-styling">Special Focus Styling</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#special-focus-styling" aria-labelledby="special-focus-styling"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card a:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">--outline-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card States"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a:is(:focus, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-826 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-826 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-826 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-826 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-826 { display: flex; justify-content: space-between; } .card__number-icon-826::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-826::before, .card__number-icon-826 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-826 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-826 a { text-decoration: none; color: var(--demo-primary); } .card-826 a::before { content: ""; position: absolute; inset: 0; } .card-826 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-826 a { font-size: inherit; } .card-826:has(.tag-826) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-826 :is(h2, h3):has(.tag-826) { display: grid; gap: 0.25em; justify-items: start; } .card-826:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-826 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-826 a:is(:hover, :focus-visible) { text-decoration: underline; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-826"> <li> <div class="card-826"> <div data-num="1" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-826">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-826"> <div data-num="2" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-826"> <div data-num="3" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-826"> <div data-num="4" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-826"> <div data-num="5" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="context-based-container-queries">Context-Based Container Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#context-based-container-queries" aria-labelledby="context-based-container-queries"><span hidden="">#</span></a></div> <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> <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> <p><img src="https://moderncss.dev/img/posts/32/spec-cards.png" alt="" /></p> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Wide variation container size query */</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <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 > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Keep other styles */</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card Container Queries"</summary> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-861 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-861 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-861 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-861 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-861 { display: flex; justify-content: space-between; } .card__number-icon-861::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-861::before, .card__number-icon-861 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-861 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-861 a { text-decoration: none; color: var(--demo-primary); } .card-861 a::before { content: ""; position: absolute; inset: 0; } .card-861 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-861 a { font-size: inherit; } .card-861:has(.tag-861) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-861 :is(h2, h3):has(.tag-861) { display: grid; gap: 0.25em; justify-items: start; } .card-861:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-861 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-861 a:is(:hover, :focus-visible) { text-decoration: underline; } .layout-861 { container: layout-container / inline-size; } @container layout-container (inline-size > 80ch) { .card-861 { aspect-ratio: 4/3; max-width: 100%; } } @container grid-item (inline-size > 35ch) { .card-861 { grid-auto-flow: column; align-items: center; justify-content: start; gap: 5cqi; aspect-ratio: unset; } .card__number-icon-861 { flex-direction: column; gap: 1rem; } } </style> <div class="demo"> <div class="demo--content"> <div class="layout-861"> <ul role="list" class="flex-layout-grid-861"> <li> <div class="card-861"> <div data-num="1" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-861">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-861"> <div data-num="2" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-861"> <div data-num="3" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-861"> <div data-num="4" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-861"> <div data-num="5" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="container-query-fluid-type">Container Query Fluid Type</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#container-query-fluid-type" aria-labelledby="container-query-fluid-type"><span hidden="">#</span></a></div> <p>According to our mockup, the last adjustment we need is to increase the font size as the card becomes wider.</p> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span></code></pre> <blockquote> <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> </blockquote> <p>One last modern CSS feature we'll use to conclude our card styles will upgrade the headings. Use of <code>text-wrap: balance</code> will evaluate a text block of up to six lines and &quot;balance&quot; 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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card Fluid Type"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <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> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-717 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-717 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-717 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-717 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-717 { display: flex; justify-content: space-between; } .card__number-icon-717::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-717::before, .card__number-icon-717 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-717 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-717 a { text-decoration: none; color: var(--demo-primary); } .card-717 a::before { content: ""; position: absolute; inset: 0; } .card-717 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-717 a { font-size: inherit; } .card-717:has(.tag-717) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-717 :is(h2, h3):has(.tag-717) { display: grid; gap: 0.25em; justify-items: start; } .card-717:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-717 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-717 a:is(:hover, :focus-visible) { text-decoration: underline; } .layout-717 { container: layout-container / inline-size; } @container layout-container (inline-size > 80ch) { .card-717 { aspect-ratio: 4/3; max-width: 100%; } } @container grid-item (inline-size > 35ch) { .card-717 { grid-auto-flow: column; align-items: center; justify-content: start; gap: 5cqi; aspect-ratio: unset; } .card__number-icon-717 { flex-direction: column; gap: 1rem; } } .card-717 :is(h2, h3) { font-size: clamp(1.25rem, 5cqi, 1.75rem); text-wrap: balance; } </style> <div class="demo"> <div class="demo--content"> <div class="layout-717"> <div class="layout-717"> <ul role="list" class="flex-layout-grid-717"> <li> <div class="card-717"> <div data-num="1" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-717">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-717"> <div data-num="2" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-717"> <div data-num="3" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-717"> <div data-num="4" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-717"> <div data-num="5" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="component-pagination">Component: Pagination</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-pagination" aria-labelledby="component-pagination"><span hidden="">#</span></a></div> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-pagination.png" alt="" /></p> <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> <p>In slightly wider spaces, the labels for the “Previous” and “Next” controls will be visible.</p> <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> <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">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/></span></span> <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">></span></span>Previous<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">></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">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token comment">&lt;!-- pagination links --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/></span></span> <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">></span></span>Next<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <p>We'll first define containment for the <code>.pagination-container</code> to enable this dynamic layout behavior.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Container Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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> <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-337 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-337 a { text-decoration: none; } .pagination-container-337, .pagination-list-337 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-337 { gap: 0.25rem; } .pagination-list-337 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-337 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-337 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-337 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-337:hover .pagination-nav__label-337 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-337 svg { transform: scaleX(-1); } .pagination-label-337 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-337 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-337 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-337 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-337 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-337 { display: grid; } .pagination-label-337 { display: none; } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-337" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-337 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-337">Previous</span> </a> <span class="pagination-label-337">Page 3 of 10</span> <ul role="list" class="pagination-list-337"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-337 pagination-nav__next-337"> <span class="pagination-nav__label-337">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <div class="heading-wrapper h3"> <h3 id="using-has-for-quantity-queries">Using <code>:has()</code> for Quantity Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#using-has-for-quantity-queries" aria-labelledby="using-has-for-quantity-queries"><span hidden="">#</span></a></div> <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> <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> <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> <p>We must place this rule within the &quot;large&quot; 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> <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> <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Quantity Queries"</summary> <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> <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-416 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-416 a { text-decoration: none; } .pagination-container-416, .pagination-list-416 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-416 { gap: 0.25rem; } .pagination-list-416 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-416 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-416 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-416 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-416:hover .pagination-nav__label-416 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-416 svg { transform: scaleX(-1); } .pagination-label-416 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-416 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-416 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-416 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-416 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-416 { display: grid; } .pagination-label-416 { display: none; } } @container (min-width: 40ch) { .pagination-container-416:has(li:nth-child(11)) { .pagination-list-416 { display: none; } .pagination-label-416 { display: block; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-416" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-416 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-416">Previous</span> </a> <span class="pagination-label-416">Page 3 of 12</span> <ul role="list" class="pagination-list-416"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">11</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">12</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-416 pagination-nav__next-416"> <span class="pagination-nav__label-416">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <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> <div class="heading-wrapper h3"> <h3 id="upgrading-to-style-queries">Upgrading to Style Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#upgrading-to-style-queries" aria-labelledby="upgrading-to-style-queries"><span hidden="">#</span></a></div> <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> <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> <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> <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> <span class="token comment">/* Styles for the container's children */</span> <span class="token punctuation">}</span></code></pre> <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> <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> <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Style Queries"</summary> <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> <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-23 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-23 a { text-decoration: none; } .pagination-container-23, .pagination-list-23 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-23 { gap: 0.25rem; } .pagination-list-23 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-23 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-23 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-23 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-23:hover .pagination-nav__label-23 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-23 svg { transform: scaleX(-1); } .pagination-label-23 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-23 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-23 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-23 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-23 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-23 { display: grid; } .pagination-label-23 { display: none; } } .pagination-container-23:has(li:nth-child(11)) { --show-label: true; } @container (min-width: 40ch) { @container style(--show-label: true) { .pagination-list-23 { display: none; } .pagination-label-23 { display: block; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-23" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-23 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-23">Previous</span> </a> <span class="pagination-label-23">Page 3 of 12</span> <ul role="list" class="pagination-list-23"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">11</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">12</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-23 pagination-nav__next-23"> <span class="pagination-nav__label-23">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <div class="heading-wrapper h2"> <h2 id="component-navigation">Component: Navigation</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-navigation" aria-labelledby="component-navigation"><span hidden="">#</span></a></div> <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 &quot;Login&quot; and &quot;Sign Up&quot; placed on the opposite side.</p> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-navigation.png" alt="" /></p> <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> <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> <p><img src="https://moderncss.dev/img/posts/32/spec-navigation.png" alt="" /></p> <p>Here's our markup outline to help understand the relationships between our elements.</p> <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">></span></span> <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">></span></span>Logo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">></span></span> <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">></span></span> Menu <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token comment">&lt;!-- link list --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token comment">&lt;!-- Login / Sign Up --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <blockquote> <p>You'll likely find that building with container queries in mind may prompt rethinking your HTML structure and simplifying the hierarchy.</p> </blockquote> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> auto 1fr auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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 >= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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 >= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Navigation Container Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> navigation <span class="token punctuation">(</span>inline-size >= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>inline-size >= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .btn-102 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-102 { color: var(--button-color, var(--demo-primary)); background-color: var(--button-bg, var(--demo-accent)); font-size: 1rem; } .btn-102:where([aria-expanded], :has(.icon-102)) { display: flex; gap: 0.5em; align-items: center; } .btn-102:where(:not(:has(.icon-102))) { text-align: center; min-inline-size: 10ch; } .btn-102:where(:not(:has(.inclusively-hidden-102))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } .btn--small-102 { --button-padding: 0.35em 0.75em; } .btn--inverse-102 { --button-bg: var(--demo-light); } .btn-102:is([aria-expanded])::after { content: ""; display: block; width: 1em; height: 0.5em; background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } .navigation-102 { margin-block: 1rem; padding: 1rem; background-color: var(--demo-light); box-shadow: 0 0.25rem 0.35rem -0.15rem rgba(166, 166, 166, 0.45); display: grid; grid-template-columns: auto 1fr auto; gap: 1rem; align-items: center; } .navigation-102 a:not(.btn-102) { text-decoration: none; padding: 0.15em 0.25em; width: fit-content; color: var(--demo-primary); border-bottom: 2px solid transparent; } .navigation-102 a:not(.btn-102):hover { background-color: var(--demo-accent); } .navigation-102 a:not(.btn-102)[aria-current] { border-bottom-color: var(--demo-accent--ui); } .navigation__brand-102 { display: inline-flex; align-items: baseline; gap: 0.25em; font-weight: bold; font-size: 1.25rem; transform: translateY(-0.1em); } .navigation__brand-102 svg { width: 1.25em; height: 1.25em; transform: translateY(0.15em); } .navigation__brand-102 span { /* Inclusively hidden so it is still read as a label by assistive tech */ height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); font-size: inherit; line-height: 0; } .navigation__menu-102 :is(button, a) { letter-spacing: 0.03em; font-weight: 600; color: var(--primary); } .navigation__menu-102 ul { gap: 0.5rem; } .btn-102:where([aria-expanded=false]) + ul { display: none; } .navigation__actions-102 { display: flex; gap: 0.5rem; align-items: center; } .navigation-102 { container: navigation / inline-size; } .navigation__menu-102 { container: menu / inline-size; } @container navigation (inline-size >= 45ch) { .navigation__brand-102 span { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container menu (inline-size >= 60ch) { .navigation__menu-102 button { display: none; } .navigation__menu-102 ul { display: flex; } } </style> <div class="demo"> <div class="demo--content"> <nav class="navigation-102"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="navigation__brand-102"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" width="32"> <mask id="a" style="mask-type: luminance" maskUnits="userSpaceOnUse" x="1" y="1" width="30" height="30"> <path d="M16 2.7a13.3 13.3 0 1 0 0 26.6V2.7Z" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16 2.7a13.3 13.3 0 1 1 0 26.6V2.7Z" fill="#555" stroke="#fff" stroke-width="2.7" stroke-linejoin="round"></path> <path d="M16 24H6m10-5.3H3.3M16 13.3H3.3M16 8H6" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> </mask> <g mask="url(#a)"><path d="M0 0h32v32H0V0Z" fill="#766C7A"></path></g> </svg> <span>Jaberwocky</span> </a> <div class="navigation__menu-102"> <button type="button" class="btn-102 btn--small-102 btn--inverse-102" aria-expanded="false" aria-controls="#menu">Menu</button> <ul id="menu" role="list"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">Features</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Pricing</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">About</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Contact</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Blog</a></li> </ul> </div> <div class="navigation__actions-102"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Login</a> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="btn-102 btn--small-102">Sign Up</a> </div> </nav> </div> </div> <blockquote> <p>Given the demo size constraints, you may not see the list until you resize the demo container larger.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="improve-scalability-with-quantity-and-style-queries">Improve Scalability With Quantity and Style Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#improve-scalability-with-quantity-and-style-queries" aria-labelledby="improve-scalability-with-quantity-and-style-queries"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</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 class="token punctuation">}</span></code></pre> <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> <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> <span class="token comment">/* Styles when the container size is between 50-80ch */</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <details false=""> <summary>CSS for "Navigation Quantity &amp; Style Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</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 class="token punctuation">}</span> <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> <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> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .btn-351 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-351 { color: var(--button-color, var(--demo-primary)); background-color: var(--button-bg, var(--demo-accent)); font-size: 1rem; } .btn-351:where([aria-expanded], :has(.icon-351)) { display: flex; gap: 0.5em; align-items: center; } .btn-351:where(:not(:has(.icon-351))) { text-align: center; min-inline-size: 10ch; } .btn-351:where(:not(:has(.inclusively-hidden-351))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } .btn--small-351 { --button-padding: 0.35em 0.75em; } .btn--inverse-351 { --button-bg: var(--demo-light); } .btn-351:is([aria-expanded])::after { content: ""; display: block; width: 1em; height: 0.5em; background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } .navigation-351 { margin-block: 1rem; padding: 1rem; background-color: var(--demo-light); box-shadow: 0 0.25rem 0.35rem -0.15rem rgba(166, 166, 166, 0.45); display: grid; grid-template-columns: auto 1fr auto; gap: 1rem; align-items: center; } .navigation-351 a:not(.btn-351) { text-decoration: none; padding: 0.15em 0.25em; width: fit-content; color: var(--demo-primary); border-bottom: 2px solid transparent; } .navigation-351 a:not(.btn-351):hover { background-color: var(--demo-accent); } .navigation-351 a:not(.btn-351)[aria-current] { border-bottom-color: var(--demo-accent--ui); } .navigation__brand-351 { display: inline-flex; align-items: baseline; gap: 0.25em; font-weight: bold; font-size: 1.25rem; transform: translateY(-0.1em); } .navigation__brand-351 svg { width: 1.25em; height: 1.25em; transform: translateY(0.15em); } .navigation__brand-351 span { /* Inclusively hidden so it is still read as a label by assistive tech */ height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); font-size: inherit; line-height: 0; } .navigation__menu-351 :is(button, a) { letter-spacing: 0.03em; font-weight: 600; color: var(--primary); } .navigation__menu-351 ul { gap: 0.5rem; } .btn-351:where([aria-expanded=false]) + ul { display: none; } .navigation__actions-351 { display: flex; gap: 0.5rem; align-items: center; } .navigation-351 { container: navigation / inline-size; } .navigation__menu-351 { container: menu / inline-size; } @container navigation (inline-size >= 45ch) { .navigation__brand-351 span { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container menu (inline-size >= 60ch) { .navigation__menu-351 button { display: none; } .navigation__menu-351 ul { display: flex; } } .navigation__menu-351:has(:nth-child(6)) { --show-menu: true; } @container menu (40ch <= inline-size <= 60ch) { @container not style(--show-menu: true) { .navigation__menu-351 button { display: none; } .navigation__menu-351 ul { display: flex; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="navigation-351"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="navigation__brand-351"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" width="32"> <mask id="a" style="mask-type: luminance" maskUnits="userSpaceOnUse" x="1" y="1" width="30" height="30"> <path d="M16 2.7a13.3 13.3 0 1 0 0 26.6V2.7Z" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16 2.7a13.3 13.3 0 1 1 0 26.6V2.7Z" fill="#555" stroke="#fff" stroke-width="2.7" stroke-linejoin="round"></path> <path d="M16 24H6m10-5.3H3.3M16 13.3H3.3M16 8H6" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> </mask> <g mask="url(#a)"><path d="M0 0h32v32H0V0Z" fill="#766C7A"></path></g> </svg> <span>Jaberwocky</span> </a> <div class="navigation__menu-351"> <button type="button" class="btn-351 btn--small-351 btn--inverse-351" aria-expanded="false" aria-controls="#menu">Menu</button> <ul id="menu" role="list"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">Features</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Pricing</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">About</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Contact</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Blog</a></li> </ul> </div> <div class="navigation__actions-351"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Login</a> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="btn-351 btn--small-351">Sign Up</a> </div> </nav> </div> </div> <div class="heading-wrapper h3"> <h3 id="container-queries-accessibility-and-fail-safe-resizing">Container Queries, Accessibility, and Fail-Safe Resizing</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#container-queries-accessibility-and-fail-safe-resizing" aria-labelledby="container-queries-accessibility-and-fail-safe-resizing"><span hidden="">#</span></a></div> <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 &quot;reflow&quot; 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> <blockquote> <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> </blockquote> <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> <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> <video controls=""> <source src="https://moderncss.dev/img/posts/32/navigation-a11y.mp4" type="video/mp4" /> </video> <blockquote> <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 &quot;breakpoints.&quot;</p> </blockquote> <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> <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> <div class="heading-wrapper h2"> <h2 id="supporting-and-using-modern-css-features">Supporting and Using Modern CSS Features</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#supporting-and-using-modern-css-features" aria-labelledby="supporting-and-using-modern-css-features"><span hidden="">#</span></a></div> <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> <p>When evaluating whether a feature is &quot;safe to use&quot; 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 &quot;nice to haves&quot; 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> <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> <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> <blockquote> <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> </blockquote> <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> <hr /> <p>This material was originally presented at <a href="https://noti.st/st3ph/ea40FC/modern-css-for-dynamic-component-based-architecture">CSS Day 2023</a> and updated for <a href="https://frontenddesignconference.com/2024/">Front-end Design Conference 2024</a>.</p> Testing Feature Support for Modern CSS 2023-05-01T00:00:00Z https://moderncss.dev/testing-feature-support-for-modern-css/ <p>The pace of the CSS language can be challenging to keep up with! Browsers release features and fixes monthly, and the CSS Working Group is constantly working on specifications. So, how do you know using a new feature is &quot;safe&quot; to use? And what are the considerations around making that choice?</p> <p>Let's review how to:</p> <ul> <li>find information on new features</li> <li>test for support</li> <li>determine when to use a feature</li> <li>decide whether a fallback is needed</li> <li>use build tools and polyfills</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="finding-out-about-new-css-features">Finding Out About New CSS Features</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#finding-out-about-new-css-features" aria-labelledby="finding-out-about-new-css-features"><span hidden="">#</span></a></div> <p>Here is a list of ways you can find out about new and upcoming CSS features:</p> <ul> <li>following the developer relations folks from various browser makers, like <a href="https://twitter.com/Una">Una Kravets</a> and <a href="https://front-end.social/@jensimmons">Jen Simmons</a></li> <li>reviewing and starring issues you're interested in being added to CSS in <a href="https://github.com/w3c/csswg-drafts">the public GitHub</a></li> <li>subscribe to the CSS Working Group (CSSWG) blog feed</li> <li>check the release notes and feature blogs from browser engines <ul> <li><a href="https://webkit.org/">Webkit</a> (Safari)</li> <li><a href="https://www.mozilla.org/en-US/firefox/releases/">Firefox</a></li> <li><a href="https://developer.chrome.com/blog/">Chrome</a></li> </ul> </li> <li>consume materials from publications and individuals who focus a lot on CSS <ul> <li><a href="https://css-irl.info/">CSS IRL</a> by Michelle Barker</li> <li><a href="https://www.miriamsuzanne.com/">Miriam Suzanne</a></li> <li><a href="https://www.bram.us/">Bramus Van Damme</a></li> <li><a href="https://chenhuijing.com/">Chen Hui Jing</a></li> <li><a href="https://piccalil.li/">Andy Bell</a></li> <li><a href="https://www.kevinpowell.co/">Kevin Powell</a></li> </ul> </li> <li>subscribe to newsletters <ul> <li><a href="https://frontendfoc.us/">Frontend Focus</a></li> <li><a href="https://csslayout.news/">CSS Layout News</a></li> <li><a href="https://codepen.io/spark">The CodePen Spark</a></li> <li><a href="https://www.smashingmagazine.com/the-smashing-newsletter/">Smashing Magazine Newsletter</a></li> <li><a href="https://css-weekly.com/">CSS Weekly</a></li> </ul> </li> </ul> <p>Additionally, browser makers have started an annual effort to improve the interoperability of the web, which means striving to make features work consistently cross-browser. You can review the list and progress on those efforts on the <a href="https://wpt.fyi/interop-2023">Interop Dashboard</a>.</p> <p>As you absorb all that's possible in CSS now, remember: it's not about learning everything right now; it's about being aware of what's possible to help you develop a solution when needed!</p> <div class="heading-wrapper h2"> <h2 id="testing-for-css-support">Testing for CSS Support</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#testing-for-css-support" aria-labelledby="testing-for-css-support"><span hidden="">#</span></a></div> <p>Testing for CSS support - also called &quot;feature detection&quot; - can be done directly in your stylesheets using <code>@supports</code>.</p> <p>This at-rule allows testing:</p> <ul> <li>properties</li> <li>values</li> <li>selectors</li> </ul> <p>Within <code>@supports</code>, the test condition will return positive if the browser understands the property and the value.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">accent-color</span><span class="token punctuation">:</span> red<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles when accent-color is supported */</span> <span class="token punctuation">}</span></code></pre> <p>You can also test for selectors such as <code>:is()</code>, <code>:where()</code>, <code>:focus-visible</code>, and more. When using the <code>selector</code> condition with a function like <code>:is()</code>, a value must also be provided to the selector.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">:is(a)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles when :is() is supported */</span> <span class="token punctuation">}</span></code></pre> <p>Like media queries, you can combine tests with <code>and</code> as well as <code>or</code>, and negate tests with <code>not</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">leading-trim</span><span class="token punctuation">:</span> both<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token punctuation">(</span><span class="token property">text-box-trim</span><span class="token punctuation">:</span> both<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when either property is supported */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">scroll-timeline-name</span><span class="token punctuation">:</span> a<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when both properties are supported */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">:focus-visible</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when :focus-visible is not supported */</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="limitations-of-supports">Limitations of <code>@supports</code></h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#limitations-of-supports" aria-labelledby="limitations-of-supports"><span hidden="">#</span></a></div> <p>A significant limitation of <code>@supports</code> is that it currently cannot test for at-rules, meaning it cannot detect support of <code>@container</code> (container queries), <code>@layer</code> (cascade layers), and others. This lack of detection is problematic because at-rules typically greatly impact how you write and structure your CSS.</p> <p>Additionally, there can be issues testing for partial implementations.</p> <p>As an example of failure for partial implementations, a recent addition to CSS is the <code>:has()</code> selector. Unfortunately, the implementation at the time of writing in Firefox 112 may return a false positive when testing relational selectors with <code>:has()</code> like <code>li:has(+ )</code>. This is false because the partial implementation only supports more direct selectors like <code>li:has(a)</code>.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* This should fail in Firefox 112 */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">li:has(+ *)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* It may not fail, so the body becomes red */</span> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* This rule does fail to apply */</span> <span class="token selector">li:has(+ *)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>When using <code>@supports</code>, be sure to test the outcome in multiple browsers to ensure your styles apply with the result you intended.</p> </blockquote> <p>Also be aware that testing your condition with <code>@supports</code> requires <code>@supports</code> itself to be supported! In other words, check the support of the feature you're testing for <em>and</em> <code>@supports</code> to ensure you're not creating a condition that wouldn't actually have the chance to fail due to <code>@supports</code> being ignored if it's unsupported.</p> <p>Don't miss the section on <a href="https://moderncss.dev/testing-feature-support-for-modern-css/#alternate-methods-of-css-feature-detection">alternate methods of CSS feature detection</a>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="deciding-on-using-a-new-feature">Deciding on Using a New Feature</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#deciding-on-using-a-new-feature" aria-labelledby="deciding-on-using-a-new-feature"><span hidden="">#</span></a></div> <p>The CSS language is growing because the web is complex, and our requirements are ever-changing. In addition, device proliferation and user needs drive a lot of change and improvements in the underlying browser engines.</p> <p>For example, it was thought that container queries would never be possible, but the availability of related features enabled their release to be cross-browser complete in February 2023.</p> <p>But when do you know it's the right time to start using a new feature? After all, while the browsers Chrome, Edge, and Firefox have been termed &quot;evergreen&quot; - meaning they can automatically update themselves - there's no guarantee that users will allow that update quickly, if at all. Safari can also update in a way decoupled from OS updates, but doing so is not actively pushed, and typically only advanced users will seek out the updates. As Eric Bailey wrote, <a href="https://css-tricks.com/evergreen-does-not-mean-immediately-available/">evergreen does not mean immediately available</a>.</p> <p>A popular resource to check for feature availability is <a href="https://caniuse.com/">caniuse.com</a>. It's a fantastic place to get an overview of when browser features are added and notes on partial implementations or known bugs. However, the percentage shown for support should be taken as one metric and used alongside your actual audience analytics.</p> <p>Depending on your location in the world, your industry, or your product's specific marketing, you may need to delay using a particular feature. Or, you might find positive signs that the majority of your audience would be able to see the latest and greatest!</p> <p>If you use VSCode, I also highly recommend <a href="https://marketplace.visualstudio.com/items?itemName=webhint.vscode-webhint">the webhint extension</a> which alerts you when you are writing a feature that may not be well supported. This saves a trip out to caniuse, as it also gives you the list of where the feature isn't supported. With that information, you can decide whether you need to create a support solution as you write your styles. This also helps in reducing bugs from appearing later in browsers you may not have tested (although you should test as much as you can!).</p> <p>The impact of the feature you're looking to integrate also weighs heavily in this decision. For example, some modern CSS features are &quot;nice to haves&quot; that provide an updated experience that's great when they work but also don't necessarily cause an interruption in the user experience when they fail.</p> <p>Some examples of low-impact features include:</p> <ul> <li><code>accent-color</code> - change the color of native form elements, including checkboxes and radio buttons</li> <li><code>::marker</code> - apply <a href="http://localhost:8081/totally-custom-list-styles/#upgrading-to-css-marker">custom list bullet or numeral styling</a> like changing the color</li> <li><code>overscroll-behavior</code> - prevent scroll chaining to the background page when the end of a scrollable container is reached</li> <li><code>scroll-margin</code> - able to add margin to the scroll position, useful for <a href="https://smolcss.dev/#smol-article-anchors">anchor targets</a></li> <li><code>text-underline-offset</code> - allows adjusting the distance between a text underline and the text</li> </ul> <p>Other features that impact layout structure, or are tied to providing a more accessible experience, may not be advised to use until you are confident in a high likelihood of support. As a quick measure, consider whether a user would be prevented in doing the tasks they need to do on your website if the modern feature fails.</p> <div class="heading-wrapper h3"> <h3 id="assigning-fallback-solutions">Assigning Fallback Solutions</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#assigning-fallback-solutions" aria-labelledby="assigning-fallback-solutions"><span hidden="">#</span></a></div> <p>Another way to reasonably use newer features is to include them alongside fallback solutions. A &quot;fallback&quot; is a solution that works well enough to retain a positive user experience when the ideal feature isn't supported.</p> <p>Fallbacks work for two reasons. First, because CSS fails silently - meaning it skips definitions it doesn't understand without breaking the whole stylesheet. Second, because of the &quot;C&quot; in CSS which is the cascade that uses the listed order of definitions as part of how the browser determines which definition to apply. The cascade rules say that - given equal specificity - the last-ordered definition that the browser understands will &quot;win&quot;.</p> <p>For example, <code>aspect-ratio</code> is an awesome feature that I enjoy using to create uniform-sized images within a grid of cards or an image gallery. A fallback may provide a height for the images so that at least they are constrained in the layout, even if the ideal <code>aspect-ratio</code> isn't used.</p> <p>The following example is from my resource SmolCSS and the &quot;<a href="https://smolcss.dev/#smol-aspect-ratio-gallery">Smol Aspect Ratio Gallery</a>&quot; demo.</p> <p>First, we assume no support and give an explicit height. Then, using <code>@supports</code> to check for <code>aspect-ratio</code> support, we remove that explicit height and then use <code>aspect-ratio</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.smol-aspect-ratio-gallery li</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>25vh<span class="token punctuation">,</span> 15rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.smol-aspect-ratio-gallery li</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--aspect-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Often fallbacks can be a one-line alternative that uses an older syntax or method. These solutions are placed just before the ideal solution, which allows the modern solution to be used where supported. And when it's not supported, the last-ordered definition that is supported will be used, which we noted earlier was due to the cascade.</p> <p>In this example, our fallback uses the well supported <code>height</code> property with <code>100vh</code>. Then, we upgrade it to use the logical property of <code>block-size</code> with <code>100dvh</code>, where <code>dvb</code> is the &quot;<a href="https://12daysofweb.dev/2022/new-viewport-units/">dynamic viewport unit</a>&quot; that is better suited for environments like iOS Safari.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Fallback */</span> <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token comment">/* Ideal, modern version */</span> <span class="token property">block-size</span><span class="token punctuation">:</span> 100dvb<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="handling-prefixed-properties">Handling Prefixed Properties</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#handling-prefixed-properties" aria-labelledby="handling-prefixed-properties"><span hidden="">#</span></a></div> <p>Sometimes, lack of support is due to one browser adopting a proprietary version of a property. When this happens, they typically use a &quot;prefix&quot;. This is how we get properties such as <code>-webkit-background-clip</code>.</p> <p>A tricky part of working with prefixed properties is that sometimes other browsers enable them to work, but they remain prefixed due to a lack of official spec support. For some properties, they eventually get spec support, leading to browsers deprecating the prefixed version. And sometimes, one browser uses a prefixed version, and the others don't!</p> <p>Luckily, a tool exists to help manage prefixing properties. Autoprefixer is available as a PostCSS plugin (which we'll discuss later) and <a href="https://autoprefixer.github.io/">as a web app</a>.</p> <p>For example, one of my favorite techniques for controlling width without affecting the display property is to use <code>width: fit-content</code>. For the best support, it needs to include prefixed versions. Rather than remembering that, I can either include Autoprefixer in my build process or use the Autoprefixer web app to get the rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.example</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> -webkit-fit-content<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> -moz-fit-content<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You'll want to check <a href="https://caniuse.com/mdn-css_properties_width_fit-content">caniuse.com</a> or the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content#browser_compatibility">browser compatibility</a> section on MDN docs to be sure that a prefixed property you want to use has support cross-browser.</p> <div class="heading-wrapper h2"> <h2 id="alternate-methods-of-css-feature-detection">Alternate Methods of CSS Feature Detection</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#alternate-methods-of-css-feature-detection" aria-labelledby="alternate-methods-of-css-feature-detection"><span hidden="">#</span></a></div> <p>Sometimes you may wish to detect features like at-rules which <code>@supports</code> is unable to do. Or, you need more precise detection for partial implementations.</p> <p>CSS at-rules are exposed as a web API that is consumable by JavaScript. This means you can check for support using JavaScript and then apply classes or other modifications to indicate to your styles that a feature is available.</p> <p>For example, you can check for support of cascade layers with the following:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>CSSLayerBlockRule<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Cascade layers are supported</span> <span class="token punctuation">}</span></code></pre> <p>A web API function that works just like <code>@supports</code> is also available, which is <code>CSS.supports()</code>. This function accepts a value identical to what you would pass to the corresponding <code>@supports</code> block, including testing for selectors and the ability to combine or negate tests.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'width: 1cqi'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Container query units are supported</span> <span class="token punctuation">}</span></code></pre> <p>When I was a young sprout coming up in web development, a popular solution for feature detection was <a href="https://modernizr.com/">Modernizr</a>. This was JavaScript that did feature tests and then added classes to the <code>&lt;html&gt;</code> element to indicate support or lack thereof. It was tremendously popular and even included in the official <a href="https://html5boilerplate.com/">HTML5 boilerplate</a>. But now, this solution is outdated, and I wouldn't recommend using it for new projects. This is because many of the tests likely aren't necessary for your audience anymore and because it hasn't been updated to include many of the very latest modern CSS features.</p> <p>However, I appreciate the ease of use of those support classes. They offload the effort of devising the right test for <code>@supports</code>, and can simplify creating selectors.</p> <p>I've created <a href="https://supportscss.dev/">SupportsCSS</a> as a feature detection solution that tests support of at-rules, selectors, and other features and applies classes to <code>&lt;html&gt;</code> with the results. The tiny script is also customizable so that it only tests for the features you care to include.</p> <p>Here's a summary of what SupportsCSS does:</p> <ul> <li>Checks for selectors like <code>:has()</code>, properties like <code>text-box-trim</code>, features like relative color syntax, and at-rules like <code>@layer</code></li> <li>Allows adding custom tests</li> <li>Exposes a results object to iterate over detected support, as well as individual results for quick conditional checks in JS</li> </ul> <p>Since the classes rely on JavaScript loading and succeeding, you will want to treat any styles based on the support classes as progressive enhancements. This is not too different from directly including <code>@supports</code> in your styles.</p> <p>However, if you have more critical styles and you <em>do</em> expect that <em>most</em> of your audience will have support, consider using a regular <code>@supports</code> block in your stylesheets. Then the styles are available as soon as your stylesheet is loaded.</p> <p>That said, you may like to review the test suite, which exposes the tests used for the features. You can copy any of the tests from the <a href="https://supportscss.dev/#test-suite">SupportsCSS test suite</a> that use <code>CSS.supports</code> and use those within <code>@supports</code>.</p> <div class="heading-wrapper h2"> <h2 id="using-build-tools-and-polyfills">Using Build Tools and Polyfills</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#using-build-tools-and-polyfills" aria-labelledby="using-build-tools-and-polyfills"><span hidden="">#</span></a></div> <p>Using <code>@supports</code> and JavaScript-based detection either directly or via <a href="https://supportscss.dev/">SupportsCSS</a> only tells you if a feature is supported. You are responsible for providing the experience for supported and unsupported features.</p> <p>Let's review polyfills and build tools that help bridge the gap while features are gaining support.</p> <div class="heading-wrapper h3"> <h3 id="polyfills">Polyfills</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#polyfills" aria-labelledby="polyfills"><span hidden="">#</span></a></div> <p>Sometimes, supporting a CSS feature is best done by including a polyfill. A polyfill is a script that enables a feature to work on an unsupported browser by creating a solution with other, better-supported features. Polyfills are used when a more simple fallback solution isn't possible or too complex to do manually.</p> <p>An example of a <a href="https://github.com/GoogleChromeLabs/container-query-polyfill">polyfill is for container queries</a>, which extends support clear back to Firefox 69, Chrome 79, Edge 79, and Safari 13.4. As with most polyfills, it has limitations and so doesn't provide full coverage of all the ways you may enact container query styles.</p> <p>Polyfills are a wonderfully helpful way to begin using &quot;future CSS&quot; today! Just be aware of their limitations. Additionally, polyfills may not keep up with syntax changes, leading to breaking a previously working implementation. You are responsible for keeping the polyfill version you include up-to-date.</p> <div class="heading-wrapper h3"> <h3 id="build-tools">Build Tools</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#build-tools" aria-labelledby="build-tools"><span hidden="">#</span></a></div> <p>We briefly mentioned Autoprefixer, which is available as a web app or PostCSS plugin. But what is <a href="https://postcss.org/">PostCSS</a>? Well, it's a tool you use alongside a build tool like Gulp, Grunt, or Webpack. Through the use of PostCSS plugins, various features become available.</p> <p>A popular PostCSS plugin is <a href="https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env">postcss-preset-env</a> which &quot;allows you to use future CSS features today.&quot; It comes coupled with Autoprefixer. When using it, polyfills are added when needed, and additional plugins related to the features you're writing are applied.</p> <p>Several tools, like PostCSS, determine how to include feature support by using the <code>browserslist</code> entry in <code>package.json</code> or by including that information in the tool's configuration. Browserslist is a way of defining which browsers your application will support, which you can visualize and adjust using the <a href="https://browsersl.ist/#q=%3E+0.2%25+and+not+dead">Browserslist web app</a>.</p> <p>Besides polyfills, transpiling is another way build tools enable support of future CSS. Transpiling means rewriting the future version to a comparable but older and better-supported version. An example would be using the logical property <code>margin-inline: auto</code> would be transpiled to <code>margin-left: auto; margin-right: auto</code> if the browserslist targets didn't have full support. This allows writing your stylesheets with newer features, which over time your build tool will stop transpiling as support improves.</p> <p>Another option besides PostCSS that I've started using as my build tool of choice is LightningCSS. It includes Autoprefixer, minification, and transpiling of new CSS. I like it because it's a single package to include and replaces the individual includes I previously had for Autoprefixer and minification. In addition, I've found that I can use it to <a href="https://thinkdobecreate.com/articles/is-it-time-to-replace-sass/">replace Sass</a> for my more simple projects since it enables nesting and still lets me organize my styles into separate files.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional Resources</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <p>I encourage you to continue learning about this topic until you are comfortable with what it means to handle modern CSS support. It's fun to experiment and practice using modern CSS, but imperitive to consider what that means for your users.</p> <p>Here are a few other resources:</p> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Conditional_Rules/Using_Feature_Queries">Using feature queries</a> from MDN</li> <li><a href="https://matthiasott.com/notes/detecting-css-selector-support-with-javascript">Detecting CSS Selector Support with Javascript</a> by Matthias Ott</li> <li><a href="https://css-irl.info/detecting-css-selector-support/">Detecting CSS Selector Support</a> by Michelle Barker</li> <li><a href="https://www.smashingmagazine.com/2019/02/css-browser-support/">A Guide to CSS SUpport in Browsers</a> by Rachel Andrew</li> <li><a href="https://css-tricks.com/how-supports-works/">How @supports Works</a> by Chris Coyier</li> <li><a href="https://css-tricks.com/evergreen-does-not-mean-immediately-available/">&quot;Evergreen&quot; Does Not Mean Immediately Available</a> by Eric Bailey</li> <li>Related topic: <a href="https://www.smashingmagazine.com/2021/10/guide-debugging-css/">A Guide to CSS Debugging</a></li> </ul> Container Query Units and Fluid Typography 2023-04-18T00:00:00Z https://moderncss.dev/container-query-units-and-fluid-typography/ <p>Fluid typography is the term for designing <code>font-size</code> rules that responsively adapt the size based on the amount of available inline space. Before the availability of container query units, techniques usually relied on the viewport width - <code>vw</code> - unit. The viewport method is excellent for main page type, such as article headlines. However, viewport-based fluid typography doesn't quite work for narrower spaces that flex independently of the viewport, such as a grid of cards.</p> <p>We'll explore three ways to create dynamic fluid typography rules by leveraging container query units and CSS custom properties. You'll learn more about:</p> <ul> <li>creating mixins using custom properties</li> <li><code>max()</code>, <code>min()</code>, <code>calc()</code> and <code>clamp()</code></li> <li>container queries and units</li> <li><code>:is()</code> and <code>:where()</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Previously here on ModernCSS, I presented a method that relied on using Sass to perform some calculations and produce the rules to apply <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">viewport-based fluid typography</a>. You may still be interested in some of the other information including tips on preventing text-overflow and a few other considerations for web typography.</p> <p>However, not only can we upgrade the solution presented previously by dropping Sass, but the final rules will be far more resilient and context-independent.</p> <div class="heading-wrapper h2"> <h2 id="fluid-typography-basics-with-clamp">Fluid Typography Basics with <code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#fluid-typography-basics-with-clamp" aria-labelledby="fluid-typography-basics-with-clamp"><span hidden="">#</span></a></div> <p>The standard viewport-based fluid typography relies on the <code>clamp()</code> function and the <code>vw</code> (viewport width) unit.</p> <p>The <code>clamp()</code> function is one of several handy <a href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/">CSS math functions</a> and accepts three values: a minimum value, an ideal value, and a maximum value. The core idea of fluid typography is that the &quot;ideal&quot; value uses a dynamic unit - <code>vw</code> - in order to interpolate between the min and max. This effectively allows the font to resize along a preferred range.</p> <p>In the demo, the minimum allowed size is <code>1rem</code> and the maximum allowed size is <code>3rem</code>, where <code>4vw</code> allows interpolating along the range.</p> <details open=""> <summary>CSS for "Viewport-based fluid typography"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4vw + 1rem<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fluid-type-459 { font-size: clamp(1rem, 4vw + 1rem, 3rem); } </style> <div class="demo"> <div class="demo--content"> <p class="fluid-type-459">Viewport-based fluid typography.</p> </div> </div> <blockquote> <p>We'll talk more about that addition of <code>1rem +</code> shortly, but suffice it to say that it enables improved resizing when using display or text zoom, an important accessibility consideration.</p> </blockquote> <p>Now although that demo has a resize handle, you won't see any resizing of the font actually occur. That's because it's reliant on the width of your viewport, so you will need to resize your entire browser width to see a change. Already, we've demonstrated the problem with this technique!</p> <p>Due to this issue, a past remedy may have been to create component-specific styles that anticipate different viewport sizes and assign various font sizes within media queries. But now, with the availability of container queries, we can do better!</p> <div class="heading-wrapper h2"> <h2 id="quick-overview-of-container-queries">Quick Overview of Container Queries</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#quick-overview-of-container-queries" aria-labelledby="quick-overview-of-container-queries"><span hidden="">#</span></a></div> <p>I've previously written both a <a href="https://12daysofweb.dev/2021/container-queries/">condensed tutorial on container queries</a> and an <a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">in-depth primer on container queries</a>.</p> <p>What you need to know to understand the examples in this tutorial is that container queries allow defining rules for elements to respond to their ancestor container's available space. This is different from media queries which can only be based on the viewport.</p> <blockquote> <p>Technically this definition and the use in this tutorial is in consideration of container <em>size</em> queries. The spec also includes <em>style</em> queries for updating rules based on style features.</p> </blockquote> <p>The primary benefit of container queries is in creating more contextually appropriate layout rules that adapt to the true available space. With viewport media queries, rules are effectively orchestrated at the macro page level. But container queries allow responding to layout changes of micro elements and components as their context shifts through variable placement in page layouts.</p> <p>Container elements must be explicitly defined, which at the base level is done through the <code>container-type</code> property. For queries against available inline space, we use the value <code>inline-size</code>. Then, child elements of the container can query the container for its size with the <code>@container</code> rule and apply styles when that size condition is met.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span>inline-size > 300px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.container .child</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>The use of the term &quot;inline&quot; rather than &quot;width&quot; is from the precedent set by <a href="https://ishadeed.com/article/css-logical-properties/">logical properties</a>, which have their orientation adjusted based on the writing mode: right to left (RTL), left to right (LTR), or vertical. Using &quot;inline&quot; refers to the horizontal dimension for the writing mode.</p> <p>As we build up the solutions, we'll learn more about working with containment.</p> <div class="heading-wrapper h3"> <h3 id="browser-support">Browser Support</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#browser-support" aria-labelledby="browser-support"><span hidden="">#</span></a></div> <p><a href="https://caniuse.com/?search=container">Container size queries and units</a> are supported from Chromium 105, Safari 16, and Firefox 110.</p> <div class="heading-wrapper h2"> <h2 id="container-query-units">Container Query Units</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#container-query-units" aria-labelledby="container-query-units"><span hidden="">#</span></a></div> <p>Officially, these are called &quot;<a href="https://www.w3.org/TR/css-contain-3/#container-lengths">container query length units</a>,&quot; and they are a measure of the size of a containing element.</p> <p>Just as <code>1vw</code> equals <code>1%</code> of the viewport width, so does <code>1cqi</code> equal <code>1%</code> of a container's inline size. We'll be using <code>cqi</code> for purposes of defining fluid typography since we want the size to be associated with the horizontal axis of the writing mode.</p> <p>Interestingly, the CSS working group resolved that all elements would default to style containment. This means that the use of a container unit will work even without an ancestor that has containment. Keep reading to learn about a quirk of this behavior.</p> <div class="heading-wrapper h2"> <h2 id="setup-custom-properties">Setup Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#setup-custom-properties" aria-labelledby="setup-custom-properties"><span hidden="">#</span></a></div> <p>To begin our solutions, we'll set up some custom properties. This is because all of our rules will be designed to work with a sort of &quot;mixin&quot; rule that will intake the cascaded values from the custom properties.</p> <p>I'm referring to it as a mixin since it will be a general rule that takes the custom properties, applies a function, and produces variable results based on the custom property values. For best results, we'll work with the cascade to more predictably inherit values. This means we'll assign our base custom property values first and the mixin rules later in the stylesheet order.</p> <p>The starting structure for our rules involves choosing explicit font sizes for headline levels 1-4, which are the main focus of our base rules. We'll create custom properties for them and explicitly assign them per headline level.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--headline-1</span><span class="token punctuation">:</span> 2.75rem<span class="token punctuation">;</span> <span class="token property">--headline-2</span><span class="token punctuation">:</span> 2.35rem<span class="token punctuation">;</span> <span class="token property">--headline-3</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--headline-4</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Note that each rule updates the <code>--font-size</code> custom property to associate it with the size for that level. That's important because it enables the mixin rules we'll be creating, which will be generalized to scale for each of the input properties. Without generalizing to a mixin, we would have to repeat the function from the mixin within each separate headline rule.</p> <div class="heading-wrapper h3"> <h3 id="mixin-selector">Mixin Selector</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-selector" aria-labelledby="mixin-selector"><span hidden="">#</span></a></div> <p>So, what does this mixin actually look like? Well, the contents will be unique per each of our three solutions. However, the selector will be the same.</p> <p>We'll attach it to each heading element as well as the added heading classes and a utility class as well of <code>.fluid-type</code>. The utility class will allow using the mixin ad-hoc for type that may not be styled as a heading.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token comment">/* unique per solution */</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token property">margin-block-end</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>For other options to assign <code>line-height</code> that you may feel better fit your final fluid type solution, review my project <a href="https://css-typography-line-height.netlify.app/">CSS Typography Line Height</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="why-use-supports">Why use <code>@supports</code>?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports" aria-labelledby="why-use-supports"><span hidden="">#</span></a></div> <p>Due to a quirk of how custom properties work, we'll need to be explicit about separating our fluid sizes that use container query units from a fallback size. Otherwise, if a browser encounters the mixin and doesn't understand the container query unit, it will throw out the custom property value.</p> <p>The &quot;quirk&quot; is that it will then render the &quot;initial&quot; value of <code>1rem</code> instead of using a previously defined size for the element. The outcome is that all type will appear at <code>1rem</code>, removing any type size hierarchy from your application. That's why we need the explicit <code>@supports</code> check for whether the browser will understand the container query units before trying to apply the rule.</p> <p>To counter this behavior, you may assign a static value to the base headline rules, <em>or</em> include a solution prior to the container queries mixin that uses viewport units. Either fallback should be placed prior to and outside of the <code>@supports</code> condition.</p> <p><a href="https://moderncss.dev/container-query-units-and-fluid-typography/#cross-browser-fluid-type">One method</a> is shown after the first mixin.</p> <div class="heading-wrapper h3"> <h3 id="why-use-is-for-the-selector">Why use <code>:is()</code> for the selector?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-is-for-the-selector" aria-labelledby="why-use-is-for-the-selector"><span hidden="">#</span></a></div> <p>Two reasons:</p> <ol> <li>The more important reason is that it will raise the specificity of the entire rule to a class, which will make it more resilient to accidental overrides from inheritance, but it can also be matched or exceeded easily by later class-based or compound rules.</li> <li>The less important reason is to simplify the selector into a single-line list vs. a traditional comma-separated selector broken into multiple lines.</li> </ol> <blockquote> <p>If you encounter issues with <code>:is()</code>, such as complexity creating overrides, or if you want the ability to override through inheritance, you can switch to <code>:where()</code>. Use of <code>:where()</code> lowers specificity to zero, meaning later rules that may be component or page specific will override it easily due to the cascade without having to match or exceed the specificity.</p> </blockquote> <p>Note that <code>:is()</code> computes to the highest specificity in the given selector list, which is why I mentioned this rule would have the specificity of a class. Based on your preference and whether the behavior of <code>:is()</code> or <code>:where()</code> is useful for your context, you can alternatively remove the wrapper and use a standard selector list without <code>:is()</code> or <code>:where()</code>.</p> <div class="heading-wrapper h2"> <h2 id="upgrade-from-vw-to-cqi">Upgrade From <code>vw</code> to <code>cqi</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#upgrade-from-vw-to-cqi" aria-labelledby="upgrade-from-vw-to-cqi"><span hidden="">#</span></a></div> <p>The cornerstone of all of our methods will be upgrading from <code>vw</code> to <code>cqi</code> as our dynamic unit of choice to enable fluid typography.</p> <p>A starting rule to do this really is just a swap of those values.</p> <details open=""> <summary>CSS for "Fluid typography using cqi"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4cqi<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fluid-type-419 { font-size: clamp(1rem, 4cqi, 3rem); } .sorry-419 { display: none; } @supports not (font-size: 1cqi) { .sorry-419 { display: block; font-weight: bold; margin-bottom: 2rem; color: firebrick; } } </style> <div class="demo"> <div class="demo--content"> <p class="sorry-419">Sorry, your browser doesn't support container query units! The demos will not be fluid.</p> <p class="fluid-type-419">Viewport-based fluid typography.</p> </div> </div> <p>But - wait a minute - it still isn't working on resize of the container. However, it is responding based on the viewport. What's going on?!</p> <p>The container queries spec includes a provision that <a href="https://www.w3.org/TR/css-contain-3/#container-queries">every element defaults to a style container</a>, which is why the use of <code>cqi</code> already enables fluid resizing. But, since we didn't define a container for our demo, the measurement is still against the closest ancestor with containment applied.</p> <p>This site doesn't have containment applied on any ancestor of the demo, so the fallback behavior of container query units is to use the &quot;<a href="https://www.w3.org/TR/css-contain-3/#container-lengths">small viewport size for that axis</a>.&quot; This means for our rule where we are querying the &quot;inline&quot; axis, the viewport width is used as the measure.</p> <p>In order to produce the effect we're really after, which is to have the font size respond to the parent container, we need to assign containment.</p> <details open=""> <summary>CSS for "Container-based fluid typography"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4cqi<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .ctr-703 { container-type: inline-size; } .fluid-type-703 { font-size: clamp(1rem, 4cqi, 3rem); } .sorry-703 { display: none; } @supports not (font-size: 1cqi) { .sorry-703 { display: block; font-weight: bold; margin-bottom: 2rem; color: firebrick; } } </style> <div class="demo"> <div class="demo--content"> <p class="sorry-703">Sorry, your browser doesn't support container query units! The demos will not be fluid.</p> <div class="ctr-703"> <p class="fluid-type-703">Viewport-based fluid typography.</p> </div> </div> </div> <p>In this update, we put a parent div with the <code>container</code> class around the paragraph. Now the paragraph with the <code>fluid-type</code> class is responsively sizing according to the demo's inline size.</p> <blockquote> <p>A key concept of container queries is that they respond to the nearest ancestor with containment. If you apply rules that use container query units and aren't seeing them respond as you expect, you may have to adjust the markup and add a rule to allow the elements to carry a container with them.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="container-units-and-text-zoom-resizing">Container Units and Text Zoom Resizing</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#container-units-and-text-zoom-resizing" aria-labelledby="container-units-and-text-zoom-resizing"><span hidden="">#</span></a></div> <p>For the example rule explaining viewport-based fluid type, I mentioned that the inclusion of <code>1rem</code> added to the <code>vw</code> value was important for text resizing. It's because viewport-based methods are prone to restricting the font size from growing until at least the 200% required by the Web Content Accessibility Guidelines (WCAG) <a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text">Success Criterion 1.4.4: Resize Text</a>.</p> <p>As <a href="https://yatil.net/blog/resize-text-reflow">clarified by Eric Eggert</a>, this rule means that the on-screen rendered pixel height of the text must eventually be able to resize up to 200% of its original height at normal (100%) zoom. That technically doesn't need to be reached by the time the browser or text zoom setting is set to 200%, so it's acceptable if it's reached by, say, 300% zoom.</p> <p>For viewport-based fluid methods, inclusion of a <code>rem</code> value helps prevent issues with the text resizing. Without it, zoom-based resizing with only <code>vw</code> is more likely to fail to increase or stall out on increasing until a very high zoom value.</p> <blockquote> <p>PS - if you're not sure why we're dealing in rems, check the <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#selecting-a-unit-for-font-size">explanation of rem vs other units</a> in the earlier article here on fluid type.</p> </blockquote> <p>An interesting feature of swapping to use <code>cqi</code> instead of <code>vw</code> is that by its very nature it will continue to increase as long as the container inline size increases during zoom. This holds true both for browser/display zoom and text zoom applied at the OS level. In my testing, as long as <code>rem</code> is still used as the anchoring unit for the <code>font-size</code> definition, increases to 200% or more are more consistently achievable than <code>vw</code> methods.</p> <p>You should always test your fluid type rules in as many ways as you can to ensure zoom behavior works as expected. This means varying zoom levels with the type in multiple contexts such as a responsive grid of cards, in a medium-width article, a full width container, and a narrow sidebar.</p> <div class="heading-wrapper h2"> <h2 id="mixin-1-dynamic-font-size-ranges-with-clamp">Mixin 1: Dynamic Font Size Ranges With <code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-1-dynamic-font-size-ranges-with-clamp" aria-labelledby="mixin-1-dynamic-font-size-ranges-with-clamp"><span hidden="">#</span></a></div> <p>Our goal is to make a mixin function, so we need to manage a few more considerations than the more static rule created in the last section.</p> <p>Let's begin the rule by plugging in our <code>--font-size</code> custom property that was previously set up. We'll also enable a <code>--font-size-fluid</code> property with a default of <code>5cqi</code>. Like the size property, this would allow updating the target size per heading level, if desired.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token comment">/* TODO: define a minimum size */</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you missed it, jump back to the explanation of <a href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports">why we're using <code>@supports</code></a>.</p> </blockquote> <p>The missing piece in our mixin function is a definition for the minimum size allowed within the range.</p> <p>One option is to assign a custom property to update per inherited rule like the other parts. But instead, let's see how we can make the value more dynamic.</p> <p>Within <code>clamp()</code>, we can perform additional math calculations, no wrapping <code>calc()</code> required!</p> <p>This update says that the minimum allowed size should be 30% smaller than the <code>--font-size</code>. Due to the mathematical order of operations, the multiplication part of the equation is computed before the subtraction.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We also slipped in one more custom property for <code>--font-size-diff</code> to enable customizing the percentage difference if needed. For example, very large font sizes might allow a greater reduction, such as <code>0.5</code>.</p> <p>This produces a very nice effect that is scalable across our heading level rules with just a few tweaks that take advantage of our additional custom properties. However, it is presently possible for the minimum size to shrink smaller than perhaps we'd like, and potentially smaller than the regular body copy.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Regular, unstyled text uses <code>1rem</code>, which is approximately <code>16px</code>, as a browser default when no zoom features are applied. We can ensure that the minimum is not less than <code>1rem</code> by comparing it to the result of the equation.</p> <p>The CSS <code>max()</code> function accepts multiple values, and the larger computed size - the &quot;max&quot; value - will be used. Therefore, by passing it <code>1rem</code> and the equation, if the computed reduction of the <code>--font-size</code> would be less than <code>1rem</code>, the browser will use <code>1rem</code> instead.</p> <p>Here's our final mixin rule with the addition of <code>max()</code>.</p> <details false=""> <summary>CSS for "Mixin for dynamic font size ranges"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--headline-1</span><span class="token punctuation">:</span> 2.75rem<span class="token punctuation">;</span> <span class="token property">--headline-2</span><span class="token punctuation">:</span> 2.35rem<span class="token punctuation">;</span> <span class="token property">--headline-3</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--headline-4</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4.5cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4.25cqi<span class="token punctuation">;</span> <span class="token property">--font-size-diff</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4cqi<span class="token punctuation">;</span> <span class="token property">--font-size-diff</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-495 { container-type: inline-size; --headline-1: 2.75rem; --headline-2: 2.35rem; --headline-3: 1.5rem; --headline-4: 1.15rem; } :is(.h1-495, .h2-495, .h3-495, .h4-495 ) { font-weight: 500; } .h1-495 { --font-size: var(--headline-1); font-size: var(--headline-1); } .h2-495 { --font-size: var(--headline-2); --font-size-fluid: 4.5cqi; font-size: var(--headline-2); } .h3-495 { --font-size: var(--headline-3); --font-size-fluid: 4.25cqi; --font-size-diff: 0.2; font-size: var(--headline-3); } .h4-495 { --font-size: var(--headline-4); --font-size-fluid: 4cqi; --font-size-diff: 0.2; font-size: var(--headline-4); } @supports (font-size: 1cqi) { .h1-495, .h2-495, .h3-495, .h4-495 { font-size: clamp( max(1rem, var(--font-size) - var(--font-size) * var(--font-size-diff, 0.3)), var(--font-size-fluid, 5cqi), var(--font-size) ); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-495"> <p class="h1-495">The five boxing wizards jump quickly.</p> <p class="h2-495">The five boxing wizards jump quickly.</p> <p class="h3-495">The five boxing wizards jump quickly.</p> <p class="h4-495">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Later in mixin #3 we'll look at a way to smooth out the transition between sizes so that it occurs more in-sync.</p> <div class="heading-wrapper h3"> <h3 id="cross-browser-fluid-type">Cross-Browser Fluid Type</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#cross-browser-fluid-type" aria-labelledby="cross-browser-fluid-type"><span hidden="">#</span></a></div> <p>To alleviate the affects of the <a href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports">custom properties quirk</a>, an alternative option would be to define the mixin using <code>vw</code> and then override it within <code>@supports</code>. You will not achieve identical results since the <code>font-size</code> will be relative to the viewport instead of individual containers, but it also allows you to have some measure of fluid type. Be sure to test cross-browser and adjust as needed!</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Adjust element `--font-size-fluid` overrides to use `vw` */</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3vw<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Add element `--font-size-fluid` overrides here that use `cqi` */</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="mixin-2-grow-from-a-base-font-size-with-calc">Mixin 2: Grow From a Base Font Size with <code>calc()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-2-grow-from-a-base-font-size-with-calc" aria-labelledby="mixin-2-grow-from-a-base-font-size-with-calc"><span hidden="">#</span></a></div> <p>In the first mixin, our use of <code>clamp()</code> allowed us to define a range for the font sizes. This is beneficial especially if you feel there needs to be a maximum for how large text can grow.</p> <p>Alternatively, if there doesn't strictly need to be an upper bound for your font sizes, we can simply allow the size to grow from a minimum base size.</p> <p>Instead of using our previously defined <code>--font-size</code>, we'll swap to defining base values. These are intended to be the smallest size we would allow, because our mixin will add on to the base.</p> <p>Here, we have listed and associated one base size per heading level, but you may prefer using semantic names like 'title', 'subtitle', 'caption', etc. Then those are assigned to the <code>--font-base-size</code> shared property for each heading rule, which will be passed into the mixin.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--h1-base</span><span class="token punctuation">:</span> 1.75rem<span class="token punctuation">;</span> <span class="token property">--h2-base</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--h3-base</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token property">--h4-base</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>You may want to retain the previous <code>--font-size</code> values to continue using as a fallback in case the fluid mixin isn't compatible with the user's browser.</p> </blockquote> <p>This mixin is quite a bit simplified from version one. Using <code>calc()</code>, we have a single equation where from the starting point of <code>--font-size-base</code> we are adding <code>--font-size-fluid</code>, which defaults to <code>3cqi</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Mixin for growth from a base size"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--h1-base</span><span class="token punctuation">:</span> 1.75rem<span class="token punctuation">;</span> <span class="token property">--h2-base</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--h3-base</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token property">--h4-base</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2.5cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2.25cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-29 { container-type: inline-size; --h1-base: 1.75rem; --h2-base: 1.5rem; --h3-base: 1.35rem; --h4-base: 1.15rem; } :is(.h1-29, .h2-29, .h3-29, .h4-29 ) { font-weight: 500; } .h1-29 { --font-size-base: var(--h1-base); font-size: var(--h1-base); } .h2-29 { --font-size-base: var(--h2-base); --font-size-fluid: 2.5cqi; font-size: var(--h2-base); } .h3-29 { --font-size-base: var(--h3-base); --font-size-fluid: 2.25cqi; font-size: var(--h3-base); } .h4-29 { --font-size-base: var(--h4-base); --font-size-fluid: 2cqi; font-size: var(--h4-base); } @supports (font-size: 1cqi) { .h1-29, .h2-29, .h3-29, .h4-29 { font-size: calc(var(--font-size-base) + var(--font-size-fluid, 3cqi)); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-29"> <p class="h1-29">The five boxing wizards jump quickly.</p> <p class="h2-29">The five boxing wizards jump quickly.</p> <p class="h3-29">The five boxing wizards jump quickly.</p> <p class="h4-29">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Using this mixin, you'll likely want to use reduced fluid values compared to the first mixin. That's because the risk of the solution so far is that font sizes can, in theory, infinitely grow based on how much inline space is available. Practically speaking, this may not cause a significant issue unless you already have a very large font base that has the potential to spread across a large inline area.</p> <p>If you do feel a maximum is eventually required, we can add one by wrapping the equation with the <code>min()</code> function and introducing a <code>--font-size-max</code> property.</p> <p>How does <code>min()</code> result in a maximum boundary? Because as the font-size grows, if the computed value tied to the <code>cqi</code> value would exceed the <code>--font-size-max</code>, that would result in <code>--font-size-max</code> being the &quot;minimum&quot; value between the options. In that way it effectively caps the growth.</p> <details open=""> <summary>CSS for "Mixin for growth until a max size"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-max<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-350 { container-type: inline-size; } .fluid-type-350 { --font-size-base: 1.35rem; --font-size-max: 3rem; font-size: min(var(--font-size-max), calc(var(--font-size-base) + var(--font-size-fluid, 3vw))); line-height: 1.2; margin-bottom: 0.65em; } @supports (font-size: 1cqi) { .fluid-type-350 { --font-size-fluid: 3cqi; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-350"> <p class="fluid-type-350">The five boxing wizards jump quickly.</p> <p class="fluid-type-350" style="--font-size-base: 1.15rem; --font-size-max: 2.5rem; --font-size-fluid: 2.5cqi;">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Now, you could extend this solution and dynamically compute the max end of the range like we did for the minimum end of the range in the first mixin. That's the beauty of custom properties used with defaults - you can choose an initial method for the mixin, and accept an override, too!</p> <div class="heading-wrapper h2"> <h2 id="mixin-3-generate-styles-using-a-type-scale-ratio">Mixin 3: Generate Styles Using a Type Scale Ratio</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-3-generate-styles-using-a-type-scale-ratio" aria-labelledby="mixin-3-generate-styles-using-a-type-scale-ratio"><span hidden="">#</span></a></div> <p>As noted in the intro, <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">fluid type has already been discussed</a> here on ModernCSS. In that tutorial, the key idea was building up font sizes according to a type scale ratio, and was computed with Sass.</p> <p>We can now take what we've learned in the other mixins and produce a comparable solution, but this time with only custom properties and CSS math functions, instead of relying on Sass!</p> <p>The idea of the ratio is to produce a collection of font sizes that feel harmonious as a group. A ratio also abstracts away the need to define individual, static font sizes since it's used to dynamically generate the sizes.</p> <p>This mixin will be very similar to the first mixin, with the difference being in how we compute the actual font size.</p> <p>Once again, we need to set up the custom properties for our base rules. We'll define a <code>--type-ratio</code> property, and have used a <a href="https://typescale.com/">&quot;perfect fourth&quot; ratio</a> as a starting point.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Perfect Fourth */</span> <span class="token property">--type-ratio</span><span class="token punctuation">:</span> 1.33<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In order for the ratio to be applied correctly, we need to compound the font sizes. This means given a base size, we'll multiply it by the ratio. Then we'll take the result and multiply it by the ratio again for the next size level, and so on.</p> <p>In the former Sass solution, we took advantage of a loop to manage the compounding. But the translation to custom properties means we'll need to do this ahead of time, so we'll add the pre-computed sizes as additional global properties.</p> <p>Our &quot;base&quot; will be the size we plan to apply to the body text so that our smallest headline is at least the first multiple of our <code>--type-ratio</code> larger than that. In this case with the perfect fourth ratio, that makes <code>--font-size-4</code> equal <code>1.33rem</code>. Each successive level takes the previous <code>--font-size-[LEVEL]</code> result and compounds it by applying the <code>--type-ratio</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Body font size */</span> <span class="token property">--body-font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Compounded headlines sizes */</span> <span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-3</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-2</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-1</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we'll assign the sizes to each headline rule. Reminder that the <code>font-size</code> listed in these rules will be used as a fallback for browsers that do not yet support container queries and units.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The mixin is a very similar calculation as the method discussed in the first solution. However, we'll compute the minimum size ahead of time. This is so that we can create a smoother transition for the group by adding the minimum + <code>1cqi</code> for the middle, ideal <code>clamp()</code> value. Since we're adding the container query unit onto the minimum, we're using a smaller value than the first mixin. Experiment and see how changing it to even a decimal value like <code>0.5cqi</code> affects the rate of change!</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--_font-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span> + 1cqi<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Additionally, we kept our friend <code>max()</code> to ensure the minimum wasn't able to reduce below the <code>--body-font-size</code>.</p> <blockquote> <p><strong>Fun fact:</strong> The <code>--_font-min</code> property doesn't need a <code>calc()</code> wrapper because at the point at which we create it as a custom property it's a simple list of values. When it gets used in <code>clamp()</code>, then the browser uses that context to actually do the calculation with the provided operators for the equation.</p> </blockquote> <p>Be sure to resize this as in a supporting browser, and also compare the transition to mixin #1.</p> <details false=""> <summary>CSS for "Mixin for generating font sizes from a type ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Perfect Fourth */</span> <span class="token property">--type-ratio</span><span class="token punctuation">:</span> 1.33<span class="token punctuation">;</span> <span class="token comment">/* Body font size */</span> <span class="token property">--body-font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Compounded headlines sizes */</span> <span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-3</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-2</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-1</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--_font-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span> + 1cqi<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-865 { container-type: inline-size; /* Perfect Fourth */ --type-ratio: 1.33; /* Body font size */ --body-font-size: 1rem; /* Compounded headlines sizes */ --font-size-4: calc(var(--body-font-size) * var(--type-ratio)); --font-size-3: calc(var(--font-size-4) * var(--type-ratio)); --font-size-2: calc(var(--font-size-3) * var(--type-ratio)); --font-size-1: calc(var(--font-size-2) * var(--type-ratio)); } :is(.h1-865, .h2-865, .h3-865, .h4-865 ) { font-weight: 500; } .h1-865 { --font-size: var(--font-size-1); font-size: var(--font-size); } .h2-865 { --font-size: var(--font-size-2); font-size: var(--font-size); } .h3-865 { --font-size: var(--font-size-3); font-size: var(--font-size); } .h4-865 { --font-size: var(--font-size-4); font-size: var(--font-size); } @supports (font-size: 1cqi) { .h1-865, .h2-865, .h3-865, .h4-865 { --_font-min: var(--font-size) - var(--font-size) * var(--font-size-diff, 0.3); font-size: clamp( max(var(--body-font-size), var(--_font-min)), var(--_font-min) + 1cqi, var(--font-size) ); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-865"> <p class="h1-865">The five boxing wizards jump quickly.</p> <p class="h2-865">The five boxing wizards jump quickly.</p> <p class="h3-865">The five boxing wizards jump quickly.</p> <p class="h4-865">The five boxing wizards jump quickly.</p> <p>Regular body copy size for a paragraph and non-headline text. </p></div> </div> </div> <p>For best results, if you would like to change the <code>--font-size-diff</code> value, you'll likely want to change it as a global property. That's because changing it for individual levels will interfere with the ratio-based sizing.</p> <p>Additionally, you can try out increasing the base for the original calculation if you feel it's too close in size to the body copy. A quick way to do that is add it into the calculation for <code>--font-size-4</code>, such as:</p> <pre class="language-css"><code class="language-css"><span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> + 0.25rem<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <blockquote> <p>As a challenge to apply what you've learned, you could adapt the second mixin that grows from a base value to use type-ratio generated base values.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="tips-on-using-the-mixins">Tips on Using the Mixins</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#tips-on-using-the-mixins" aria-labelledby="tips-on-using-the-mixins"><span hidden="">#</span></a></div> <p>Practically speaking, when using any of the mixins presented in this tutorial you will possibly want to create containers out of elements like <code>&lt;article&gt;</code> or create a utility class to apply containment. And, where containment is applied will be something to consider when you define markup for components like cards. Otherwise, as we learned, it may seem as though the viewport is being used to compute the font size rather than your intended context.</p> <p>While our mixin rules are being applied broadly to headlines, you may prefer to <em>only</em> apply fluid type when a utility class is used. Or, you may determine a few variations that better fit your specific contexts and components, such as scales for articles, cards, forms, and tables.</p> <div class="heading-wrapper h3"> <h3 id="which-one-should-i-use">Which One Should I Use?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#which-one-should-i-use" aria-labelledby="which-one-should-i-use"><span hidden="">#</span></a></div> <p>Since all the mixins use the container query unit of <code>cqi</code> to trigger expanding and shrinking of the font size, your context and preferences will be the deciding factors.</p> <p>Perhaps you feel expanding from a base is easier to reason about, or produces the results you're after more consistently for a particular component, so you use mixin number two. Or, maybe you like defining the ranges more precisely, or have been given those ranges in design specs, so mixin number one that uses <code>clamp()</code> better fits your style. And maybe you just prefer to leave the sizes up to math, so providing a type scale like mixin number three works best for you.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources-on-fluid-typography">Additional Resources on Fluid Typography</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#additional-resources-on-fluid-typography" aria-labelledby="additional-resources-on-fluid-typography"><span hidden="">#</span></a></div> <p>Fluid type is far from a new topic, and the methods presented here are not the only ways to accomplish it! I've learned a lot from the following resources, and I encourage you to continue researching to find the technique that suits your preference or project best.</p> <p><strong>Most resources use viewport-based calculations</strong> since container query units are a more recent addition to the web platform. As such, they may need adapted if you prefer basing the sizing on containers.</p> <ul> <li>Skip any of the manual calculations and use values provided by <a href="https://utopia.fyi/type/calculator/">Utopia.fyi</a> (currently viewport-based only)</li> <li>A thorough <a href="https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/">review of fluid typography with clamp()</a> by Adrian Bece</li> <li>An alternative to the mixins here is this solution from Andy Bell for <a href="https://archive.hankchizljaw.com/wrote/custom-property-controlled-fluid-type-sizing/">custom property controlled fluid type sizing</a></li> <li>Scott Kellum of Typetura presents this innovative method of <a href="https://css-tricks.com/intrinsic-typography-is-the-future-of-styling-text-on-the-web/">deriving font sizes using keyframe animation</a> to do the interpolation</li> </ul> Contextual Spacing For Intrinsic Web Design 2022-05-03T00:00:00Z https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/ <p>The user's browsing environment is not predictable. Tell other developers, and for goodness sakes, tell your designers. Let's learn how to coexist with that unpredictability by using adaptive, contextual spacing techniques.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>In 2018, Jen Simmons introduced the term &quot;Intrinsic Web Design&quot; in her talk &quot;<a href="https://talks.jensimmons.com/videos/h0XWcf">Everything You Know About Web Design Just Changed</a>.&quot; She also shared the principles of intrinsic web design that we'll use as guidance:</p> <ol> <li><strong>Contracting &amp; Expanding</strong> - the way we consider how our design will adapt to a change in available space</li> <li><strong>Flexibility</strong> - using primarily flexbox and grid in combination with newer units and functions in a way that enables our layouts to adapt at various rates to the available space</li> <li><strong>Viewport</strong> - the ability to use all four sides of the viewport as well as take advantage of viewport units</li> </ol> <blockquote> <p>Using adaptive layout techniques is a trust exercise between designers, developers, and the browser.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="properties-and-functions-for-intrinsic-design">Properties and functions for intrinsic design</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#properties-and-functions-for-intrinsic-design" aria-labelledby="properties-and-functions-for-intrinsic-design"><span hidden="">#</span></a></div> <p>Let's start with a review of the foundational essentials for creating intrinsicly sized elements.</p> <div class="heading-wrapper h3"> <h3 id="clamp">Clamp</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#clamp" aria-labelledby="clamp"><span hidden="">#</span></a></div> <p>A versatile CSS function that is key to intrinsic web design is <code>clamp()</code>, and it has had <a href="https://caniuse.com/css-math-functions">stable support since March 2020</a>.</p> <p>Clamp accepts three values: the minimum, ideal, and maximum values. This effectively lets you provide flexible constraints.</p> <p>The trick with <code>clamp()</code> is in that ideal value where a dynamic unit such as view width must be used to trigger the transition between the min and max.</p> <p>You may have encountered <code>clamp()</code> under the umbrella of fluid typography, which relies on viewport units. Here's a set of example values:</p> <pre class="language-css"><code class="language-css"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4vw<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Based on the current computed value of 4vw, the <code>font-size</code> will adjust as the viewport grows and shrinks. But it will never be smaller than <code>1rem</code> or larger than <code>3rem</code>.</p> <p>We'll review more use cases for <code>clamp</code> later on for our spacing techniques.</p> <p>So how do we go about using <code>clamp()</code> for intrinsic design? My suggestion is:</p> <ul> <li><strong>designers</strong> provide the min and max values</li> <li><strong>devs</strong> determine dynamic dimensions</li> </ul> <p>The <em>min</em> and <em>max</em> values can be provided by design tokens, which may be a familiar concept if you come from design systems or are using frameworks that provide sizing ramps. I'll continue to call out opportunities for design tokens as we explore more techniques since they are an excellent method to map our constraints back to designs and wireframes.</p> <div class="heading-wrapper h3"> <h3 id="min-and-max-functions">Min and Max functions</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#min-and-max-functions" aria-labelledby="min-and-max-functions"><span hidden="">#</span></a></div> <p>The <code>min()</code> and <code>max()</code> functions enable us to provide context-dependent options. These have essentially the same level of support as <code>clamp()</code>.</p> <p>Both <code>min()</code> and <code>max()</code> accept two or more values. For the <code>min()</code> function, the browser will use the smallest computed value, and the inverse will happen for <code>max()</code> where the browser will use the largest computed value.</p> <p>Another feature of <code>min</code>, <code>max</code>, and <code>clamp</code> is that we can perform additional calculations without needing a nested <code>calc()</code> function. So for the following definition, we're asking the browser to choose the smallest value between <code>100vw - 3rem</code> and <code>80ch</code>.</p> <pre class="language-css"><code class="language-css"><span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span></code></pre> <p>This results in <code>100vw - 3rem</code> being selected when the viewport is &lt; <code>80ch</code>, and <code>80ch</code> being selected when the viewport is &gt; <code>80ch</code>.</p> <p>If we take that rule and add the logical property <code>margin-inline</code> set to <code>auto</code>, then we have a really modern container class.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We can take our container class a step further and set up an optional custom property of <code>container-max</code> that uses <code>80ch</code> as a fallback. So now we have a modern, ultra-flexible rule.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--container-max<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It can be a bit tricky to understand when to use <code>min</code> and <code>max</code>.</p> <p>Let's consider the following values for <code>max</code>.</p> <pre class="language-css"><code class="language-css"><span class="token function">max</span><span class="token punctuation">(</span>2rem<span class="token punctuation">,</span> 4vh<span class="token punctuation">)</span></code></pre> <p>When the computed value of <code>4vh</code> becomes less than <code>2rem</code>, the <code>max</code> function will select <code>2rem</code>.</p> <p>So effectively, what's happening is that the selected choice of <code>2rem</code> is the minimum value you will allow for this rule.</p> <p>Now let's flip to <code>min()</code>:</p> <pre class="language-css"><code class="language-css"><span class="token function">min</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> 60ch<span class="token punctuation">)</span></code></pre> <p>This rule for <code>min()</code> means that 60ch is effectively the maximum allowed value for this element.</p> <p>If you're wondering why we'd use <code>min</code> and <code>max</code> instead of listing out separate corresponding properties, it's because we can use them anywhere a numeric value is allowed, not just for dimensions.</p> <p>For example, <code>background-size</code> like we reviewed in <a href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/">practical uses of CSS math functions</a> where we explored more about <code>clamp()</code>, <code>min()</code>, and <code>max()</code>.</p> <div class="promo"><p><em>Hey there!</em> Registration is open for my July workshop "Level-Up With Modern CSS" &mdash; <a href="https://smashingconf.com/online-workshops/workshops/stephanie-eckles-july/"><strong>register today!</strong></a></p></div> <div class="heading-wrapper h3"> <h3 id="fitminmax-content">Fit/Min/Max-Content</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#fitminmax-content" aria-labelledby="fitminmax-content"><span hidden="">#</span></a></div> <p>Next up, we have <code>fit-content</code>, <code>min-content</code>, and <code>max-content</code>, which allow intrinsic sizing.</p> <p><a href="https://caniuse.com/intrinsic-width">Support for these keywords</a> is best when paired with the <code>width</code> property, and you may find the need to use prefixes depending on your audience.</p> <p>Let's compare how these sizing keywords render for text content when applied to the <code>width</code> property:</p> <details open=""> <summary>CSS for "Intrinsic Keywords Comparison"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fit-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.min-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> min-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.max-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> max-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fit-content-675 { width: fit-content; } .min-content-675 { width: min-content; } .max-content-675 { width: max-content; } .keywords-wrap-675 { padding: 1rem; background-color: thistle; } .keywords-675 { line-height: 1.1; background-color: rebeccapurple; color: white; font-size: 1.25rem; } .keywords-675 + .keywords-675 { margin-top: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="keywords-wrap-675"> <p class="keywords-675 fit-content-675">width set to fit-content</p> <p class="keywords-675 min-content-675">width set to min-content</p> <p class="keywords-675 max-content-675">width set to max-content</p> </div> </div> </div> <ul> <li><code>fit-content</code> grows just large enough to contain its contents</li> <li><code>min-content</code> only grows large enough to match the width of the longest word and will apply soft-wrapping</li> <li><code>max-content</code> will continue growing as large as its contents require</li> </ul> <p>At first look, it can be hard to tell the difference between <code>fit-content</code> and <code>max-content</code>, so let's expand the text values:</p> <style> .fitt-content-37 { width: fit-content; } .maxx-content-37 { width: max-content; } .keywords-wrap-37 { padding: 1rem; background-color: thistle; max-width: 25ch; outline: -.5rem-37 solid dashed; } .keywords-37 { line-height: 1.1; background-color: rebeccapurple; color: white; font-size: 1.25rem; } .keywords-37 + .keywords-37 { margin-top: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="keywords-wrap-37"> <p class="keywords-37 fitt-content-37">fit-content will grow but not overflow</p> <p class="keywords-37 maxx-content-37">max-content has overflow potential</p> </div> </div> </div> <p>To be honest, I haven't found many use cases for <code>min-content</code> or <code>max-content</code>. But <code>fit-content</code> is a top-shelf property.</p> <p>In this demo, the &quot;alert&quot; has <code>width: fit-content</code>. Notice that it is only growing to the equivalent of its <code>max-content</code>, which is narrower than the available inline space.</p> <style> .wrap-843 { padding: 1rem; background-color: thistle; } .article-843 { padding: clamp(0.5rem, 3%, 1.5rem); width: min(100%, 60ch); margin-inline: auto; background-color: #fff; } .alert-843 { width: -moz-fit-content; width: fit-content; background-color: rebeccapurple; color: #fff; padding: 0.5rem .75rem; border-radius: 0.5rem; margin-block: 1rem; } .placeholder-843 { background-image: repeating-linear-gradient(to bottom, #B5B8B8 0 1rem, #fff 1rem 1.75rem); height: 4.5rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-843"> <div class="article-843"> <div class="placeholder-843"></div> <p class="alert-843">Lorem ipsum, dolor sit amet.</p> <div class="placeholder-843"></div> </div> </div> </div> </div> <p>The magic of <code>fit-content</code> is achieving content-relative width without changing the <code>display</code> value, meaning we can save the <code>display</code> property. And in this flow context, the alert element can continue to use block behavior which means margins continue to work.</p> <div class="heading-wrapper h3"> <h3 id="grid-units-and-functions">Grid Units and Functions</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#grid-units-and-functions" aria-labelledby="grid-units-and-functions"><span hidden="">#</span></a></div> <p>Folks - we've had <a href="https://caniuse.com/css-grid">CSS grid support</a> since spring 2017.</p> <blockquote> <p>CSS Grid is the perfect toolset for achieving flexibility within constraints.</p> </blockquote> <p>Under the topic of intrinsic web design, we will review my top two uses of grid.</p> <p>The following is the most magical CSS definition because it creates an intrinsically sized layout grid.</p> <pre class="language-css"><code class="language-css"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>30ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>I've written about it in several other Modern CSS articles, but here's the summary. Except for the <code>ch</code> unit, everything in that line is CSS grid-specific.</p> <ul> <li><code>repeat()</code> defines a recurring pattern to use for the specified grid tracks</li> <li><code>auto-fit</code> means to create as many tracks as will fit, according to the next part of the definition</li> <li><code>minmax()</code> is a CSS grid function that defines a range of values that will be used to compute each track size where the first value is the minimum and the second value is the maximum</li> </ul> <p>Here's the rule in action:</p> <details open=""> <summary>CSS for "intrinsic grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.grid-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <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>--grid-min<span class="token punctuation">,</span> 20ch<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-818 { padding: 1rem; background-color: thistle; } .grid-layout-818 { list-style: none; padding: 5%; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 20ch)), 1fr)); gap: 1rem; width: min(100%, 80ch); margin-inline: auto; } .grid-layout-818 li { font-size: 2rem; display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; background-color: rebeccapurple; color: white; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-818"> <ul class="grid-layout-818"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> </div> </div> </div> <p>At a larger size, three tracks are created, and as the inline space reduces, the track space also reduces. Once track size squeezes elements below the <code>30ch</code> minimum, elements drop to be added to or create a new row.</p> <p>In the demo, we also enhanced the rule to include a nested <code>min()</code> function with <code>100%</code> as one of the options. This reduces the chance of overflow by allowing the element to shrink below <code>30ch</code> when the space requires being more narrow. We also enabled this rule to scale by adding an optional custom property of <code>--grid-min</code> to control the &quot;breakpoint.&quot;</p> <p>In this next rule, we'll use the grid-only function version of <code>fit-content()</code>. Using the <code>fit-content()</code> function means we can provide our own preferred max value, as seen for the sidebar in the demo, which maxes out at <code>20ch</code>.</p> <details open=""> <summary>CSS for "fit-content() grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">fit-content</span><span class="token punctuation">(</span>20ch<span class="token punctuation">)</span> <span class="token function">minmax</span><span class="token punctuation">(</span>50%<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-368 { padding: 1rem; background-color: thistle; } .sidebar-layout-368 { display: grid; grid-template-columns: fit-content(20ch) minmax(50%, 1fr); gap: 2rem; width: min(100%, 80ch); margin-inline: auto; } .sidebar-layout-368 > * { display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; padding: 2vw; background-color: rgba(255, 255, 255, 0.15); } .sidebar-layout-368 .article-368 p { font-size: 2.25rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-368"> <div class="sidebar-layout-368"> <div class="sidebar"> Sidebar content goes here </div> <div class="article-368"> <p>Article</p> </div> </div> </div> </div> </div> <p>Altogether, the behavior produced by this rule is that the article element will reduce until it hits its 50% minimum. At this point, the sidebar will finally begin to compress until it reaches the equivalent of <code>min-content</code>.</p> <p>We can improve the flexibility of this rule by again including a custom property. I love this definition because it can accommodate other content types, like this image.</p> <details open=""> <summary>CSS for "fit-content() grid layout with image"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">fit-content</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--sidebar-max<span class="token punctuation">,</span> 20ch<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">minmax</span><span class="token punctuation">(</span>50%<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-763 { padding: 1rem; background-color: thistle; } .sidebar-layout-763 { display: grid; grid-template-columns: fit-content(var(--sidebar-max, 20ch)) minmax(50%, 1fr); gap: 2rem; width: min(100%, 80ch); margin-inline: auto; } .sidebar-layout-763 .article-763 { display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; padding: 2vw; background-color: rgba(255, 255, 255, 0.15); } .sidebar-layout-763 .article-763 p { font-size: 2.25rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-763"> <div class="sidebar-layout-763"> <div class="sidebar"> <img src="https://assets.codepen.io/1101822/coffee.jpeg" /> </div> <div class="article-763"> <p>Article</p> </div> </div> </div> </div> </div> <p>Just keep in mind that the image doesn't have such a thing as a min-content value, so it will continue to shrink based on the remaining allotment of space.</p> <div class="heading-wrapper h2"> <h2 id="intrinsic-contextual-spacing">Intrinsic, contextual spacing</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#intrinsic-contextual-spacing" aria-labelledby="intrinsic-contextual-spacing"><span hidden="">#</span></a></div> <p>So far, what we've talked about is how to affect how elements take up space and size themselves. Now it's finally time to talk about affecting the spacing between and around elements which will help us get the most out of intrinsic web design.</p> <p>Despite the long-time availability of units besides the pixel, we continue to define spacing almost exclusively in pixels, maybe sometimes in rems.</p> <p>Pixels are the least flexible unit for creating intrinsic layouts. So what if we could provide the browser with a better rubric for determining sizes for spacing? And what if we could do it without being overbearing with media queries?</p> <p>The first thing to acknowledge regarding spacing is that the properties involved in spacing - gap, padding, and margin - have different purposes. So let's learn how to use more appropriate units and create adaptive methods for handling spacing between and around elements.</p> <div class="heading-wrapper h3"> <h3 id="padding">Padding</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#padding" aria-labelledby="padding"><span hidden="">#</span></a></div> <p>Padding is for handling individual box spacing. Our upgraded technique will use <code>clamp()</code> with percents and <code>rem</code>.</p> <p>A reminder that percents used with padding are calculated relative to the element's inline size, so for <code>clamp()</code>, we can use a percentage as an element-relative dynamic value.</p> <p>Here's a comparison of two mobile experiences - the left uses pixels to define margin and padding, and the right uses <code>clamp()</code>:</p> <p><img src="https://moderncss.dev/img/posts/29/clamp-card.png" alt="" /></p> <p>For the version using <code>clamp()</code>, notice the gains in inline space, which leads to a more comfortable reading experience.</p> <p>Pixels are often designed to work for a &quot;desktop&quot; or widescreen environment. And updating their values would mean creating a series of media-query controlled breakpoints. So instead, here's a demo of our improvement using <code>clamp()</code> and<code> min()</code>.</p> <p>We use the same padding rule for both the article element and the cards. The trick on the article is to toggle between <code>60ch</code>, which applies on large inline spaces, and <code>100%</code>, which results in no extra outside &quot;gutter&quot; space on narrow inline spaces.</p> <p>Another context impacted positively by these rules is the Web Content Accessibility Guidelines Success Criterion for reflow, which defines expectations for browser zoom up to 400%. At this point, the computed width of the screen is assumed to be around 320px. Unfortunately, we don't have a zoom media query, but any rules that affect a viewport approaching 320px will impact this context. So, the narrow layout will display at the high zoom and be allowed the more ideal line length for reading.</p> <blockquote> <p>Learn more about <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">reflow and other modern CSS upgrades to improve accessibility</a></p> </blockquote> <p>I recommend trying out creating padding custom properties.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--padding-sm</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 3%<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--padding-md</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6%<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--padding-lg</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>3rem<span class="token punctuation">,</span> 12%<span class="token punctuation">,</span> 6rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>That middle percent value may seem a bit magic - and, well, it is - but I've found a decent starting point is to double the maximum value to use as the percent. And hey, there are some more design token opportunities!</p> <div class="heading-wrapper h3"> <h3 id="margin">Margin</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#margin" aria-labelledby="margin"><span hidden="">#</span></a></div> <p>Next up is margin, and for our purposes, we'll be explicitly using it for block layout spacing, by which I mean vertical spacing.</p> <p>We'll use the <code>min()</code> function with viewport units and <code>rem</code> for margin. Viewport units will allow creating contextual spacing.</p> <p>Here is our baseline rule for margin;</p> <pre class="language-css"><code class="language-css"><span class="token selector">.block-flow</span> <span class="token punctuation">{</span> <span class="token property">margin-block-start</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>4rem<span class="token punctuation">,</span> 8vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>I'm borrowing the term &quot;flow&quot; from Andy Bell's flow rule and &quot;block&quot; because the rule is applied using the logical property of <code>margin-block-start</code>. If you're not yet familiar with logical properties, <code>margin-block-start</code> is the logical companion to <code>margin-top</code>.</p> <p>Within <code>min()</code>, we've supplied the values of <code>4rem</code> and <code>8vh</code>. So you can think of <code>4rem</code> as the static value and <code>8vh</code> as the dynamic value.</p> <p>Here's the rule's effect on a large vs. small context. Technically, the computed margin for these values only saves about <code>13px</code> on the &quot;mobile&quot; version, which may not seem too impactful.</p> <p><img src="https://moderncss.dev/img/posts/29/block-flow.png" alt="the .block-flow rule as viewed on a laptop vs. a mobile phone" /></p> <p>However, once again, in our zoom context, the difference made by enabling the <code>8vh</code> option is quite positive.</p> <p><img src="https://moderncss.dev/img/posts/29/block-flow-zoom.png" alt="" /></p> <p>In this context, it's desirable to reduce unnecessary space. This demo also emphasizes the difference between a zoom context and mobile: landscape orientation. In contrast, typically, we're most concerned about designing for portrait orientation in the mobile context.</p> <p>So here is a starting point for our block-flow custom properties, where once again, simply doubling the static <code>rem</code> value to produce the <code>vh</code> value works out pretty well. And as we explored, both true mobile and desktop zoom are the critical contexts to test out and verify the <code>vh</code> value. The static <code>rem</code> value is another design token opportunity.</p> <p>Since I need to stay on brand, here’s your ultra modern CSS rule to setup block flow.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:is(body, .block-flow) > * + *</span> <span class="token punctuation">{</span> <span class="token property">margin-block-start</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>- -block-flow<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>- -block-flow-md<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Using <code>:is()</code>, we're defining that direct children of the <code>body</code> and direct children when the <code>block-flow</code> class is applied will default to having our medium top margin. If including <code>body</code> is too overarching for your context, you can certainly reduce this rule to just <code>block-flow</code>.</p> <p>Here is the starter set of <code>block-flow</code> custom properties:</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--block-flow-sm</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>2rem<span class="token punctuation">,</span> 4vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--block-flow-md</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>4rem<span class="token punctuation">,</span> 8vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--block-flow-lg</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>8rem<span class="token punctuation">,</span> 16vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="gap">Gap</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#gap" aria-labelledby="gap"><span hidden="">#</span></a></div> <p>Our final spacing property is <code>gap</code>, which we'll consider for layout component spacing, as in providing values for the intrinsic grid we set up earlier. We'll again be using <code>clamp()</code>, but switch it up to use <code>vmax</code> as the dynamic value for this context.</p> <p>As a reminder, <code>gap</code> is applied between elements, as the shaded area indicates.</p> <p><img src="https://moderncss.dev/img/posts/29/gap.jpeg" alt="three text elements with row and column gap space indicated by a pink shaded area" /></p> <p>And by the way - we've had <a href="https://caniuse.com/flexbox-gap">cross-browser support for <code>gap</code> in flexbox</a> since April 2021!</p> <p>Here's a baseline rule for layout <code>gap</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">gap</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6vmax<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>vmax</code> unit request the browser to use whichever is largest: the viewport width or the height.</p> <p>We're using <code>vmax</code> for the dynamic unit and not percent because percent, as applied to <code>gap</code>, is calculated based on the direction of the <code>gap</code>. So uniformly applying <code>gap</code> like this using percent may yield a smaller value for row <code>gap</code> than for column <code>gap</code>. Instead, using <code>vmax</code> creates dynamic, contextual space that will be evenly applied to both row and column <code>gap</code>.</p> <p>And - you probably guessed it - our min and max values for <code>clamp()</code> are once again potential design tokens.</p> <p>I use <code>gap</code> with grid and flex for atomics like form fields. So I wanted to explicitly name this set of custom properties as <code>layout-gap</code> since they rely on <code>vmax</code> and are intended for layout components like grids. Again, you can see the doubling strategy in effect to work out a starting <code>vmax</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--layout-gap-sm</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 3vmax<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span> <span class="token property">--layout-gap-md</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6vmax<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--layout-gap-lg</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>3rem<span class="token punctuation">,</span> 8vmax<span class="token punctuation">,</span> 4rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="why-not-use-media-queries">Why not use media queries?</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#why-not-use-media-queries" aria-labelledby="why-not-use-media-queries"><span hidden="">#</span></a></div> <p>We've just reviewed more appropriate techniques to ensure context-dependent, intrinsic sizing and spacing of elements. Viewport-relative media queries aren't scalable for every context and are no longer the best tool for the job.</p> <p>Additionally, we'll soon have full support for container queries. Once we have access to container units, they may become the better option in some places I've still opted for viewport units in these examples.</p> <p>We are also getting the &quot;parent&quot; selector - <code>:has()</code> - which will open more possibilities for creating contextual spacing based on the actual element configuration.</p> <div class="heading-wrapper h2"> <h2 id="start-using-contextual-spacing-techniques">Start using contextual spacing techniques</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#start-using-contextual-spacing-techniques" aria-labelledby="start-using-contextual-spacing-techniques"><span hidden="">#</span></a></div> <p>Design systems and frameworks' (necessary) rigidity is at odds with intrinsic web design.</p> <p>In order to move, I'm asking each of you to try to make adjustments where you can and educate others about the possibilities. Take time to be a little more thoughtful, experiment, and push beyond your current comfort zone. Let me know how you use and improve this starter set of techniques!</p> <blockquote> <p>Check out this <a href="https://codepen.io/collection/ZMrONd/9d25332426833a3571274e21a2bde3c8">CodePen collection of a few selected methods</a>, including a layout that pulls several techniques together.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional resources</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <p>This article is based on my presentation from beyond tellerrand 2022. Check out <a href="https://noti.st/st3ph/YWbRfR/scaling-css-layout-beyond-pixels">the recording and slides</a>.</p> <p>Here are some resources and other viewpoints on intrinsic web design/adaptive layout.</p> <ul> <li>Jen Simmons' talks <ul> <li><a href="https://aneventapart.com/news/post/modern-layouts-getting-out-of-our-ruts-by-jen-simmons-an-event-apart-video">Getting Out Of Our Ruts (2016)</a></li> <li><a href="https://talks.jensimmons.com/videos/h0XWcf">Everything You Know About Web Design Just Changed (2018)</a></li> <li><a href="https://www.youtube.com/watch?v=AMPKmh98XLY">Designing for Intrinsic Layouts (2020)</a></li> </ul> </li> <li>Rachel Andrew - <a href="https://aneventapart.com/news/post/making-things-better-aea-video">Making Things Better: Redefining the Technical Possibilities of CSS</a></li> <li>Ethan Marcotte's <a href="https://aneventapart.com/news/post/ethan-marcotte-a-dao-of-flexibility-video">A Dao of Flexibility</a> - the talk that introduced responsive design</li> <li>John Allsopp - <a href="https://alistapart.com/article/dao/">A Dao of Web Design</a></li> <li>Jeremy Keith <ul> <li><a href="https://adactio.com/journal/18982">Declarative design</a></li> <li><a href="https://resilientwebdesign.com/">Resilient Web Design</a></li> </ul> </li> <li>Andy Bell and Heydon Pickering - <a href="https://every-layout.dev/">Every Layout</a></li> <li>Andy Bell - <a href="https://buildexcellentwebsit.es/">BuildExcellentWebsit.es</a></li> <li>Cathy Dutton - <a href="https://alistapart.com/article/designing-for-the-unexpected/">Designing For The Unexpected</a></li> <li>Hidde de Vries - <a href="https://hidde.blog/content-based-grid-tracks-and-embracing-flexibility/">Content-based grid tracks and embracing flexibility</a></li> <li>Donnie D'Amato - <a href="https://gridless.design/">gridless.design</a></li> </ul> Practical Uses of CSS Math Functions: calc, clamp, min, max 2021-08-15T00:00:00Z https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/ <p>There are currently <a href="https://caniuse.com/css-math-functions">four well-supported math functions in CSS</a>. I've found each of them to be extremely useful in my daily work. These CSS functions can be used in perhaps unexpected ways, such as within gradients and color functions and in combination with CSS custom properties. We'll learn the syntax for each, view basic demos of their functionality, and explore practical use cases.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="calc"><code>calc()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#calc" aria-labelledby="calc"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>calc()</code></strong>: performing basic math operations, with the ability to interpolate between unit types (ex. <code>rem</code> to <code>vw</code>).</p> </blockquote> <p>This math function has the longest cross-browser support of the four functions we're exploring. It has a wide range of uses for any time you'd like to be able to do client-side math within your styles.</p> <p>For example, you may want something to take up most of the viewport height except the height of the navigation. For this purpose, you can mix units to pass a relative <code>vh</code> (view height) unit with an absolute pixel unit:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.content</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vh - 60px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As the viewport resizes or a user visits on larger or smaller devices, the value of <code>100vh</code> will dynamically update, and therefore so will the calculation.</p> <blockquote> <p>The benefit of <code>calc()</code> is in allowing you to avoid either hard-coding a range of magic numbers or adding a JavaScript solution to calculate the value needed to apply it as an inline style.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="using-calc-for-generative-color-palettes">Using <code>calc()</code> For Generative Color Palettes</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-calc-for-generative-color-palettes" aria-labelledby="using-calc-for-generative-color-palettes"><span hidden="">#</span></a></div> <p>We can extend the capabilities of <code>calc()</code> by passing in CSS custom properties.</p> <p>An example of this being very useful is creating a consistent color palette using <code>hsl()</code> (which stands for hue, saturation, and lightness). Given values for saturation, lightness, and a starting hue, we can calculate complementary values to build a full palette. Because of the commonality among the saturation and lightness values, the palette will feel cohesive.</p> <details open=""> <summary>CSS for "Using calc() to create an HSL color palette"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.colors</span> <span class="token punctuation">{</span> <span class="token property">--base-hue</span><span class="token punctuation">:</span> 140<span class="token punctuation">;</span> <span class="token property">--saturation</span><span class="token punctuation">:</span> 95%<span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 80%<span class="token punctuation">;</span> <span class="token property">--rotation</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #222<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--saturation<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--lightness<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color1</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color2</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--rotation<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color3</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--rotation<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .colors-974 { --base-hue: 140; --saturation: 95%; --lightness: 80%; --rotation: 60; color: #222; text-align: center; } .color-974 { padding: 0.25rem; background-color: hsl(var(--hue), var(--saturation), var(--lightness)); } .color1-974 { --hue: calc(var(--base-hue)); } .color2-974 { --hue: calc(var(--base-hue) + var(--rotation)); } .color3-974 { --hue: calc(var(--base-hue) + var(--rotation) * 2); } </style> <div class="demo no-resize"> <div class="demo--content"> <ul class="colors-974"> <li class="color-974 color1-974">Color 1</li> <li class="color-974 color2-974">Color 2</li> <li class="color-974 color3-974">Color 3</li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="clamp"><code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#clamp" aria-labelledby="clamp"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>clamp()</code></strong>: setting boundaries on a range of acceptable values.</p> </blockquote> <p>The <code>clamp()</code> function takes three values, and order matters. The first is the lowest value in your range, the middle is your ideal value, and the third is the highest value in your range.</p> <p>An area you may have already encountered the use of <code>clamp()</code> is for fluid typography. The essential concept is that the <code>font-size</code> value can fluidly adjust based on the viewport size. This is intended to prevent large headlines triggering overflow, or taking up too much of the viewport.</p> <p>A very basic definition for a fluid <code>h1</code> style:</p> <pre class="language-css"><code class="language-css"><span class="token selector">h1</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.75rem<span class="token punctuation">,</span> 4vw + 1rem<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">read more about generating fluid type in the Modern CSS episode 12</a>.</p> <div class="heading-wrapper h3"> <h3 id="relative-responsive-padding-with-clamp">Relative Responsive Padding With <code>clamp()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#relative-responsive-padding-with-clamp" aria-labelledby="relative-responsive-padding-with-clamp"><span hidden="">#</span></a></div> <p>Another example can be seen in <a href="https://smolcss.dev/#smol-responsive-padding">my demo from SmolCSS on responsive padding</a>. The interesting thing about using percentages for padding is that it is relative to the element's width. This means it's a bit like a container-relative unit, which we can use similar to how you might think of <code>vw</code>.</p> <p>The example from SmolCSS uses the following padding definition, where the padding will grow and shrink relative to the element's width. It will never be less than <code>1rem</code>, and never greater than <code>3rem</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.5rem <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 5%<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You may have realized this again removes some scenarios where you might have previously reached for media queries. Instead of micro-managing this spacing or worrying about strictly adhering to a pixel ramp (ex 8, 12, 24, 36), you can set up sensible guidelines for a responsive transition.</p> <p>The most significant benefit here versus media queries is that since this padding definition is element relative, it will be larger when the element has more space on the page and smaller if, for example, it's placed in a narrow column. This would take a lot of coordination with media-query-based utility classes!</p> <div class="heading-wrapper h2"> <h2 id="min"><code>min()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#min" aria-labelledby="min"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>min()</code></strong>: setting boundaries on the maximum allowed value in a way that encompasses the responsive context of an element.</p> </blockquote> <p>That's right - despite being the <code>min()</code> function, the outcome is that the provided values will act as a <em>maximum</em> allowed value for the property.</p> <p>Given <code>width: min(80ch, 100vw)</code>, the outcome is that on a larger viewport, the <code>80ch</code> will be selected because it is the smaller value of the two options, yet it <em>acts like</em> a maximum based on contextually available space. Once the viewport shrinks, <code>100vw</code> will be used because it is computed as <em>smaller than</em> <code>80ch</code>, yet it's actually providing a <em>maximum</em> boundary for the element's width.</p> <div class="heading-wrapper h3"> <h3 id="the-modern-css-container-class">The Modern CSS <code>.container</code> Class</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#the-modern-css-container-class" aria-labelledby="the-modern-css-container-class"><span hidden="">#</span></a></div> <p>The example just provided is my preferred way to define a <code>.container</code>, with one tiny tweak. The <code>min()</code> function allows nested basic math operations, which means we can flip to subtracting some space as a swap for defining left and right padding, as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>80ch<span class="token punctuation">,</span> 100vw - 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On larger viewports, the element can grow to a <em>max</em> of <code>80ch</code>, and once the viewport shrinks below that width, it will be allowed to grow to <code>100vw - 2rem</code>. This definition effectively produces <code>1rem</code> of &quot;padding&quot; on either side of the element.</p> <p>In this example, you could also swap to <code>100%</code> instead of <code>vw</code> to make the element width responsive within a <em>parent container</em>, as used for this demo:</p> <details open=""> <summary>CSS for "The Modern CSS .container Class"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>40ch<span class="token punctuation">,</span> 100% - 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-531 { width: min(40ch, 100% - 2rem); margin-right: auto; margin-left: auto; padding: 1rem; color: #222; outline: 1px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="container-531"> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vero a quam labore inventore iste eligendi, quasi velit, qui repellendus voluptatem temporibus nisi. Pariatur nesciunt at dolorum, cumque illum maiores animi?</p> </div> </div> </div> <blockquote> <p><em>Quick note</em>: The <code>ch</code> unit is equal to the width of the <code>0</code> character given all current <code>font</code> properties at the time it is applied. This makes it an excellent choice for approximating line length for a better reading experience, for example.</p> </blockquote> <p><strong>What's the benefit</strong>? Responsive sizing <em>without</em> the need for media queries! It seems to be a common theme for these functions 😉</p> <p>The <code>min()</code> function is my most used of the math functions. Let's look at some more amazing upgrades to practical scenarios.</p> <div class="heading-wrapper h3"> <h3 id="responsive-element-sizing-with-min">Responsive Element Sizing with <code>min()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#responsive-element-sizing-with-min" aria-labelledby="responsive-element-sizing-with-min"><span hidden="">#</span></a></div> <p><strong>Any time you would like to size an element responsively, <code>min()</code> can be a great choice</strong>. For example, I explored <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/">using <code>min()</code> to control sizing an avatar</a> within a comment thread in Modern CSS episode 26.</p> <p>In the avatar example, we ended up applying <em>three</em> values with different units: <code>min(64px, 15%, 10vw)</code>. Another way to read this is that the avatar size will not exceed one of those values at any given time, with the browser selecting whichever is the <em>minimum</em> computed value.</p> <p>This definition works out to never having an avatar larger than <code>64px</code>. Particularly in a zoom scenario, the <code>10vw</code> helps the size feel more relative. And the <code>15%</code> helps keep the size relative to the element, which may have a more visually appealing result before the <code>10vw</code> applies.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="using-min-within-other-properties">Using <code>min()</code> Within Other Properties</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-min-within-other-properties" aria-labelledby="using-min-within-other-properties"><span hidden="">#</span></a></div> <p>CSS math functions can be used in most properties that allow a numeric value. One unique place to use them is within <code>background-size</code>.</p> <p>Why? Perhaps you're supplying a layered effect of a background color and an image. And rather than using the <code>cover</code> size value, which would make the image fill the space, you would like to cap the growth of the image. This is a perfect place to bring in <code>min()</code>.</p> <p>Consider the following example, where <code>min()</code> is used to ensure the image doesn't exceed <code>600px</code> while being allowed to respond down with the element by also setting <code>100%</code>. In other words, it will grow <em>up to</em> <code>600px</code> and then resize itself down to match the element's width when it is less than <code>600px</code>.</p> <details open=""> <summary>CSS for "Controlling background-size with min()"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.background-image</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> #1F1B1C <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://source.unsplash.com/RapCPd_mJTU/800x800<span class="token punctuation">)</span></span> no-repeat center<span class="token punctuation">;</span> <span class="token property">background-size</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>600px<span class="token punctuation">,</span> 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .background-image-924 { display: grid; place-items: center; grid-template-areas: "background"; background: #1F1B1C url(https://source.unsplash.com/RapCPd_mJTU/800x800) no-repeat center; background-size: min(600px, 100%); box-shadow: inset 600px 600px rgba(0, 0, 0, 0.45); } .background-image-924 p { display: grid; place-content: center; grid-area: background; width: min(600px, 100%); outline: 1px dashed; min-height: 8rem; color: white; padding: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="background-image-924"> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vero a quam labore inventore iste eligendi, quasi velit.</p> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="max"><code>max()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#max" aria-labelledby="max"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>max()</code></strong>: setting boundaries on the minimum allowed value in a way that encompasses the responsive context of an element.</p> </blockquote> <p>Yup, <code>max()</code> is the opposite of <code>min()</code>! So now we are setting up definitions for the <em>minimum</em> allowed values. Let's get right to the examples!</p> <div class="heading-wrapper h3"> <h3 id="contextual-margins-with-max">Contextual Margins with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#contextual-margins-with-max" aria-labelledby="contextual-margins-with-max"><span hidden="">#</span></a></div> <p>After learning about the <a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">Web Content Accessibility Guidelines (WCAG) Success Criterion 1.4.10</a> for reflow, which states that a user should be able to use zoom to magnify your site to 400%, I noticed that pixels and rems become a subpar unit in that context.</p> <p>Given a desktop size of 1280px at 400% zoom, your content is equivalent to a device at 320px. However - versus a mobile phone - the orientation is still landscape. A viewport of this size means a much-reduced area to read and perform actions. Additionally, sizes that seemed appropriate for a phone become a lot large contextually when viewed in a zoomed-in window.</p> <p>Fortunately, <code>max()</code> gives us one way to in particular handle margins more gracefully. I avoid pixel values for everything in my personal work and usually prefer <code>rem</code> for smaller spaces. But for larger spaces intended to separate content sections, I use the following, which allows relative growth for tall viewports and reduces distance for shorter viewports, which also applies to zoomed viewports.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.element + .element</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>8vh<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On the taller viewports, <code>8vh</code> will be used, and on smaller or zoomed-in viewports, <code>2rem</code> will be used. I encourage you to try this out and spend some time testing across viewports, devices, and with and without zooming into your layout. This technique is a small upgrade that can make a significant difference for the end-user.</p> <blockquote> <p>Review an expanded example of this scenario and <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">learn more about reflow</a> in the Modern CSS episode 27.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="prevent-browser-zoom-on-inputs-in-ios-with-max">Prevent Browser Zoom on Inputs in iOS with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#prevent-browser-zoom-on-inputs-in-ios-with-max" aria-labelledby="prevent-browser-zoom-on-inputs-in-ios-with-max"><span hidden="">#</span></a></div> <p>Have you ever experienced forced browser zoom once you focused a form input on iOS? This consequence will happen for any input that has a font-size less than <code>16px</code>.</p> <p>Here's the fix, originally linked in <a href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/">Modern CSS episode 21 about custom form input styles</a>, with full credit to <a href="https://dev.to/danburzo/css-micro-tip-make-mobile-safari-not-have-to-zoom-into-inputs-1fc1">Dan Burzo</a> for this simple solution:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>16px<span class="token punctuation">,</span> 1rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Where <code>1rem</code> could be swapped with a Sass variable or a CSS custom property. This use of <code>max()</code> ensures that regardless of another value provided, the <code>font-size</code> will be <em>at least</em> <code>16px</code> and therefore prevent the forced browser zoom.</p> <div class="heading-wrapper h3"> <h3 id="relative-focus-outlines-with-max">Relative Focus Outlines with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#relative-focus-outlines-with-max" aria-labelledby="relative-focus-outlines-with-max"><span hidden="">#</span></a></div> <p>The latest addition to my CSS reset uses <code>min()</code> to apply relative sizing for focus outlines.</p> <p>This is a reduced snippet, but by using <code>max()</code>, we ensure a <em>minimum</em> outline size of <code>2px</code>, while allowing it to <em>grow</em> relative to the element by using the font-relative <code>em</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">a</span> <span class="token punctuation">{</span> <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.08em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--outline-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">--outline-color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><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 function">var</span><span class="token punctuation">(</span>--outline-style<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">outline-offset</span><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></code></pre> <div class="heading-wrapper h3"> <h3 id="accessible-target-sizes-with-max">Accessible Target Sizes with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#accessible-target-sizes-with-max" aria-labelledby="accessible-target-sizes-with-max"><span hidden="">#</span></a></div> <p>The term &quot;target size&quot; comes from <a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html">WCAG Success Criterion (SC) 2.5.5</a>, where &quot;target&quot; refers to the area that will receive a pointer event (ex. mouse click or touch tap). In the upcoming WCAG 2.2, SC 2.5.5 is now the &quot;Enhanced&quot; version, which has a minimum size of <code>44px</code>.</p> <p>For this guideline, consider buttons that only use icons or the avatar from our earlier example that links to a profile. Or perhaps a dual-action button where a dropdown arrow is a separate action from the primary button control.</p> <p>In these instances, we can use <code>max()</code> similarly to when we provided a guardrail to prevent the input zooming. We'll set <code>44px</code> as one of the values within <code>max()</code> so that at <em>minimum</em>, that is the element's size.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.icon-button</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> 2em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> 2em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It should be noted that this criterion also considers the space around the element, which if combined with the element's actual size is <em>at least</em> 44px, then the criterion is passed successfully. As with all of these techniques, be sure to test with your actual product and with real users!</p> <div class="heading-wrapper h3"> <h3 id="using-max-as-a-fallback-for-css-aspect-ratio">Using <code>max()</code> As A Fallback for CSS <code>aspect-ratio</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-max-as-a-fallback-for-css-aspect-ratio" aria-labelledby="using-max-as-a-fallback-for-css-aspect-ratio"><span hidden="">#</span></a></div> <p>Another way I've used <code>max()</code> is to set an image height when using <code>aspect-ratio</code> to enable an acceptable experience for browsers that do not yet support that property.</p> <p>You can see the following sample fully in use for the <a href="https://smolcss.dev/#smol-card-component">SmolCSS demo for a composable card component</a>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* Fallback for `aspect-ratio` of defining a height */</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>18vh<span class="token punctuation">,</span> 12rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* When supported, use `aspect-ratio` */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--img-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="putting-it-all-together">Putting It All Together</h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#putting-it-all-together" aria-labelledby="putting-it-all-together"><span hidden="">#</span></a></div> <p>This final demo shows an example of applying multiple CSS math functions to allow responsive sizing across several properties. Note the comments alongside the demonstrated code.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="WNpXxVV" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>For more examples of using these CSS math functions and other modern CSS features, <a href="https://www.youtube.com/watch?v=dz6aFfme_hg">check out my talk from CSS Cafe on YouTube</a>.</p> </blockquote> Modern CSS Upgrades To Improve Accessibility 2021-04-09T00:00:00Z https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/ <p>Accessibility is a critical skill for developers doing work at any point in the stack. For front-end tasks, modern CSS provides capabilities we can leverage to make layouts more accessibly inclusive for users of all abilities across any device.</p> <p>This post will cover a range of topics:</p> <ul> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility">Focus Visibility</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-vs-source-order">Focus vs. Source Order</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">Desktop Zoom and Reflow</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#sizing-interactive-targets">Sizing Interactive Targets</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#respecting-color-and-contrast-settings">Respecting Color and Contrast Settings</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#accessibility-learning-resources">Accessibility Learning Resources</a></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="what-does-accessible-mean">What Does &quot;Accessible&quot; Mean?</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#what-does-accessible-mean" aria-labelledby="what-does-accessible-mean"><span hidden="">#</span></a></div> <p>Accessible websites are ones that are created without barriers for users of various abilities to access content or perform actions. An internationally agreed-upon standard called the <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines</a> - or WCAG - provides success criteria to help guide you towards creating accessible experiences.</p> <p>Common accessibility barriers include:</p> <ul> <li>inability to see content or distinguish interface elements due to poor color contrast</li> <li>reduced or removed access to non-text content such as within images or charts due to failing to provide alternative text</li> <li>trapping keyboard users due to not managing focus for interactive elements</li> <li>causing headaches or worse for users with vestibular disorders due to motion and flashing/blinking animations</li> <li>preventing users of assistive technology such as screen readers from performing actions due to failure to make custom controls accommodate expected patterns</li> <li>limiting common assistive technology navigation methods due to not using semantic HTML, including heading hierarchy and landmark elements</li> </ul> <p><a href="https://www.w3.org/WAI/WCAG22/Understanding/understanding-techniques">Success criteria</a> &quot;are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc.&quot; We are going to examine a few success criteria and how modern CSS helps provide accessible solutions.</p> <div class="heading-wrapper h2"> <h2 id="focus-visibility">Focus Visibility</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility" aria-labelledby="focus-visibility"><span hidden="">#</span></a></div> <p>An all too common violation that I have done myself in the past is to remove <code>:focus</code> outlines on links, buttons, and other interactive controls. Without providing an alternative <code>:focus</code> style, this is immediately a violation of the <a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance-minimum.html">WCAG Success Criterion 2.4.11: Focus Appearance</a>.</p> <p>Frequently, the reason this is removed is due to feeling the native browser style is not attractive or doesn't fit in with the design choices of the meta. But with modern CSS, we have a new property that can help make outlines more appealing.</p> <p>Using <code>outline-offset</code>, we can provide a positive value to position the outline away from the element. For the offset, we'll use the <code>em</code> unit to position the outline relative to the element based on its <code>font-size</code>.</p> <blockquote> <p>Bonus: We're setting the <code>outline-width</code> value using the <code>max()</code> function to ensure it doesn't shrink below a computed value of <code>1px</code> while allowing it to also be relatively sized using <code>em</code>.</p> </blockquote> <p><em>Select the demo button to display the <code>:focus</code> outline</em>.</p> <details open=""> <summary>CSS for "Focus Styles With Positive `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-359 { color: purple; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-359:focus { outline: max(1px, 0.1em) solid currentColor; outline-offset: 0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-359">Demo Button</button></p> </div> </div> <p>Alternatively, set <code>outline-offset</code> using a negative value to inset the outline from the element's perimeter.</p> <details open=""> <summary>CSS for "Focus Styles With Negative `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-260 { background-color: purple; color: white; padding: 0.5em; border-radius: 4px; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-260:focus { outline: max(1px, 0.1em) dashed currentColor; outline-offset: -0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-260">Demo Button</button></p> </div> </div> <p>There is also a new pseudo-class that you can consider using in some circumstances. The <code>:focus-visible</code> pseudo-class will display an outline (or user-defined style) only when the device/browser (user agent) determines it needs to be visible. Typically this means it will appear for keyboard users upon <code>tab</code> key interaction but not for mouse users.</p> <p>Using this update, our button styles will <em>likely</em> only show when you keyboard tab into the button.</p> <details open=""> <summary>CSS for "Focus Styles With Negative `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-99 { background-color: blue; color: white; padding: 0.5em; border-radius: 4px; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-99:focus { outline: none; } .outline-offset-99:focus-visible { outline: max(1px, 0.1em) dashed currentColor; outline-offset: -0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-99">Demo Button</button></p> </div> </div> <p>Note that <a href="https://caniuse.com/css-focus-visible"><code>:focus-visible</code> support</a> is still rolling out to all browsers, notably missing from Safari. If you would like to try using it, here is an example of including it as a progressive enhancement.</p> <p>We're taking advantage of the fact that a browser that doesn't understand <code>:focus-visible</code> will throw away the rule that removes the outline for <code>:focus</code>. Meaning, the first rule for <code>button:focus</code> will apply to browsers that don't support <code>:focus-visible</code>, and the second two rules will apply when <code>:focus-visible</code> is supported. Interestingly, the <code>:focus:not(:focus-visible)</code> gives a false impression that <code>:focus-visible</code> is working in Safari <em>and even</em> Internet Explorer 11.</p> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus:not(:focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="focus-vs-source-order">Focus vs. Source Order</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-vs-source-order" aria-labelledby="focus-vs-source-order"><span hidden="">#</span></a></div> <p>Another focus related criterion is <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-order">Success Criterion 2.4.3: Focus Order</a>. For both visual and non-visual users, the focus order - which is typically initiated by keyboard tabbing - should proceed logically. Particularly for visual users, the focus order should follow an expected path which <em>usually</em> means following source order.</p> <p>From the criterion documentation linked previously:</p> <blockquote> <p>&quot;<em>For example, a screen reader user interacts with the programmatically determined reading order, while a sighted keyboard user interacts with the visual presentation of the Web page. Care should be taken so that the focus order makes sense to both of these sets of users and does not appear to either of them to jump around randomly.</em>&quot;</p> </blockquote> <p>Modern CSS technically provides layout properties to re-arrange visual order into something different than source order. The impact of this is potentially failing the focus order success criterion <em>if</em> there are focusable elements that are being re-arranged into a surprising order.</p> <p>If you are able, browse the following demo using your tab key and take notice of what you <em>expected</em> to happen versus what <em>actually</em> happened. When you're finished, or if a tab key is not presently an available input for you, check the box to reveal the tab order.</p> <style> [class*="source-order"] { color: #222; } .source-order-51 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; list-style: none; } .source-order-51 li { padding: 1rem; text-align: center; } .source-order-51 li a { display: grid; place-content: center; color: inherit; } .source-order__label-51 { font-weight: bold; } .source-order__label-51, .source-order__checkbox-51 { display: inline-block; margin-top: 0.5rem; margin-left: 0.5rem; margin-bottom: 1.5rem; } .source-order__checkbox-51:checked ~ ol { list-style: decimal; list-style-position: inside; } .source-order-51 li:nth-child(2) { grid-column: 3; grid-row: 2; } .source-order-51 li:nth-child(5) { grid-column: 2; grid-row: 1; } .source-order-51 li:nth-child(7) { grid-column: 1; grid-row: 1; } .source-order-51 li:nth-child(3) { grid-column: 1; grid-row: 3; } </style> <div class="demo no-resize"> <div class="demo--content"> <label class="source-order__label-51" for="source-order-51">Reveal order</label> <input class="source-order__checkbox-51" type="checkbox" id="source-order-51" /> <ol class="source-order-51"> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> </ol> </div> </div> <p>The goal of this section is more to provide awareness of this criterion when you consider how to solve layout challenges. Upcoming in CSS grid is a native &quot;masonry&quot; solution. Unfortunately, it may negatively impact the expected focus order. Similar issues can be created when assigning specific grid areas that don't match source order, as well as customizing item order in flexbox using the <code>order</code> property.</p> <p>A recent challenge I faced was a list of navigation links that was requested to break into columns. And in this case, the logical tab order would be down one column before moving on to the next column. The catch was that it was a list of items for a single topic, so semantically it would not be ideal to break it into a list per column. Breaking into multiple lists would also misrepresent the number of items for the single topic for users of assistive tech like screen readers which announce the number of items in a list.</p> <p>Using the following set of CSS grid properties, I was able to arrive at a non-list-breaking solution:</p> <details false=""> <summary>CSS for "List Focus Order Solution With CSS Grid"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-column-gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">grid-row-gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Causes items to be ordered into columns */</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token comment">/* # required to prevent a column per link */</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Size the columns */</span> <span class="token property">grid-auto-columns</span><span class="token punctuation">:</span> <span class="token function">minmax</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .grid-list-focus-order-57 { list-style: none; padding: 2rem; display: grid; grid-column-gap: 2rem; grid-row-gap: 1rem; grid-auto-flow: column; grid-template-rows: repeat(3, 1fr); grid-auto-columns: minmax(0, 33%); } .grid-list-focus-order-57 a { color: blue; } </style> <div class="demo"> <div class="demo--content"> <ul class="grid-list-focus-order-57"> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 1</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 2</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 3</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 4</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 5</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 6</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 7</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 8</a></li> </ul> </div> </div> <p>The caution here is to be mindful of the space necessary to accommodate the content. In a navigation scenario, content is usually fairly tightly controlled, so this can be a reasonable solution.</p> <blockquote> <p>Manuel Matuzovic has an excellent and more thorough <a href="https://www.matuzo.at/blog/the-dark-side-of-the-grid-part-2/">guide to considerations of CSS Grid layout and altering source order</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="desktop-zoom-and-reflow">Desktop Zoom and Reflow</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow" aria-labelledby="desktop-zoom-and-reflow"><span hidden="">#</span></a></div> <p>You've tested across viewport sizes using browser dev tools as well as on real mobile devices, and you're happy with your site's responsive behavior. But you're probably missing one test point: desktop zoom.</p> <p>In <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410---reflow">the previous tutorial</a>, we started to look at <a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">WCAG Success Criterion 1.4.10 - Reflow</a>.</p> <blockquote> <p>Reflow is the term for supporting desktop zoom up to 400%. On a 1280px wide resolution at 400%, the viewport content is equivalent to 320 CSS pixels wide. The intent of a user with this setting is to trigger content to <em>reflow</em> into a single column for ease of reading.</p> </blockquote> <p>As also noted in the previous tutorial, typically zoom begins to trigger responsive behavior that you may have set using media queries. But there is currently no zoom media query. Consequently, the aspect ratio of a desktop zoomed to 400% can cause <em>reflow</em> issues with your content.</p> <p>Some examples of possible issues:</p> <ul> <li>sticky navigation that covers half <em>or more</em> of the viewport</li> <li>contained scroll areas that assume a mobile portrait aspect ratio become unscrollable/cut-off</li> <li>unwanted results when using <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">fluid typography</a> techniques</li> <li>overflow or overlap issues that cut-off content</li> <li>margin and padding spacing appearing too large relative to the content size</li> </ul> <p>Without a zoom media query, it can be difficult to devise zoom solutions that are independent of device size assumptions.</p> <p>However, with modern CSS functions like <code>min()</code> and <code>max()</code>, we have tools to resolve some zoom instances without detracting from the original intent of our assumed mobile design.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>We previously looked at <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410---reflow">using <code>min</code> to adjust a grid column</a> that contained an avatar in a way that worked for small, large, and zoomed viewports.</p> <p>Let's look at resolving vertical spacing. A common practice is for designers to create a pixel ramp for spacing, perhaps based on an 8px unit. So you would perhaps create spacing utilities that look like:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.margin-top-xs</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-sm</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-md</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 32px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-lg</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-xl</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 128px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And so on to accommodate for the full range of your ramp. This is usually fine across a standard range of devices. But consider that the <code>xl</code> value of <code>128px</code> on a desktop zoomed to 400% becomes <em>half the viewport height</em>.</p> <p>Instead, we can update the upper end of the range to add in <code>min()</code> to select the <em>lowest computed value</em>. This means that for non-zoomed viewports, <code>128px</code> will be used. And for 400% zoomed viewports, an alternative viewport unit value may be used.</p> <p>If you are using a desktop, try out zooming to see the effect on the space between the demo elements:</p> <details open=""> <summary>CSS for "Resolving Margin Spacing For Zoom"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">section + section</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>128px<span class="token punctuation">,</span> 15vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .zoom-margin-spacing-487 { min-height: 150px; padding: 1rem; margin: 0 1rem; outline: 1px solid purple; outline-offset: -.5rem; color: #222; } .zoom-margin-spacing-487 + .zoom-margin-spacing-487 { margin-top: min(128px, 25vh); } </style> <div class="demo no-resize"> <div class="demo--content"> <section class="zoom-margin-spacing-487"> <h3>Section 1</h3> </section> <section class="zoom-margin-spacing-487"> <h3>Section 2</h3> </section> <section class="zoom-margin-spacing-487"> <h3>Section 3</h3> </section> </div> </div> <p>This technique can potentially be used if you encounter overlap from absolute positioned elements as well.</p> <blockquote> <p>For more examples of how zoom can effect layout and some modern CSS solutions, check out the recording of CSS Cafe meetup talk about &quot;<a href="https://www.youtube.com/watch?v=dz6aFfme_hg">Modern CSS Solutions to (Previously) Complex Problems</a>&quot;</p> </blockquote> <p>In the next section we'll see how to query for touch devices. Using that can make for an excellent combo to help determine the context of a user and present an alternate layout for things like sticky navigation or custom scroll areas.</p> <div class="heading-wrapper h2"> <h2 id="sizing-interactive-targets">Sizing Interactive Targets</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#sizing-interactive-targets" aria-labelledby="sizing-interactive-targets"><span hidden="">#</span></a></div> <p>Our next area is to consider properly sizing interactive targets, where the term &quot;target&quot; comes from <a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size.html">Success Criterion 2.5.5: Target Size</a>. From that criterion:</p> <blockquote> <p>&quot;<em>The intent of this success criterion is to ensure that target sizes are large enough for users to easily activate them, even if the user is accessing content on a small handheld touch screen device, has limited dexterity, or has trouble activating small targets for other reasons. For instance, mice and similar pointing devices can be hard to use for these users, and a larger target will help them activate the target.</em>&quot;</p> </blockquote> <p>Being introduced in WCAG 2.2 is <a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-target-spacing.html">Success Criterion 2.5.8: Pointer Target Spacing</a>. Together, the guidance indicates that generally interactive controls should either:</p> <ul> <li>have a minimum actual size of 44px; <em>or</em></li> <li>allowed a minimum target size - inclusive of spacing between the control and other elements - of 44px</li> </ul> <p><em>There are exceptions and additional examples located in the linked resources to help clarify this requirement. Adrian Roselli also provides <a href="https://adrianroselli.com/2019/06/target-size-and-2-5-5.html">an excellent overview and history</a>.</em></p> <p>In addition to this site, I maintain <a href="https://smolcss.dev/">SmolCSS.dev</a> which explores minimal modern CSS solutions to create layouts and components. The following is an excerpt from one of the demonstrations - &quot;<a href="https://smolcss.dev/#smol-avatar-list">Smol Avatar List Component</a>&quot;.</p> <p>It uses the CSS function <code>max()</code> to ensure that the default display of the avatar link column is <em>at least</em> <code>44px</code> regardless of the value that may be passed within the <code>--avatar-size</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.smol-avatar-list</span> <span class="token punctuation">{</span> <span class="token property">--avatar-size</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">--avatar-count</span><span class="token punctuation">:</span> 3<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token comment">/* Default to displaying most of the avatar to enable easier access on touch devices `max` ensures the WCAG touch target size is met or exceeded */</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--avatar-count<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--avatar-size<span class="token punctuation">)</span> / 1.15<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, when a hover-capable device that also is likely to take a &quot;fine&quot; input such as a mouse or stylus is detected, the solution allows the avatars to overlap. This allowance is due to an animation on <code>:hover</code> and <code>:focus</code> - two interactions not available for touch-only devices - that reveals the avatar fully and meets the target size in those states.</p> <p>This snippet shows the media query that allows that detection:</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">any-hover</span><span class="token punctuation">:</span> hover<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">any-pointer</span><span class="token punctuation">:</span> fine<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Allow avatars to overlap by shrinking grid cell width */</span> <span class="token punctuation">}</span></code></pre> <p>Using device capability detection, we can offer experiences that allow meeting this criterion while also still allowing design flexibility.</p> <blockquote> <p><strong>Always test solutions</strong> with real devices based on data about your real users.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="reducing-motion">Reducing Motion</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#reducing-motion" aria-labelledby="reducing-motion"><span hidden="">#</span></a></div> <p>Some of your users may have vestibular disorders and are at risk of headaches or even seizures due to motion and flashing/blinking animations.</p> <p>There are three main criteria:</p> <ul> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html">Success Criterion 2.3.1 Three Flashes or Below Threshold</a> - Web pages do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds.</li> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes">Success Criterion 2.3.2: Three Flashes</a> - Web pages do not contain anything that flashes more than three times in any one second period</li> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions">Success Criterion 2.3.3: Animation from Interactions</a> - Motion animation triggered by interaction can be disabled, unless the animation is essential to the functionality or the information being conveyed.</li> </ul> <blockquote> <p>For more comprehensive information, Val Head is a leading expert and has written extensively about this criteria, including this <a href="https://css-tricks.com/accessible-web-animation-the-wcag-on-animation-explained/">CSS-Tricks article covering the motion-related WCAG criteria</a>.</p> </blockquote> <p>Modern CSS gives us a media feature query that we can use to test for a users OS-set preference about motion. While your base animations/transitions should meet the flash related thresholds as noted in WCAG criteria, you should prevent them entirely if a user prefers reduced motion.</p> <p>Here's a quick rule set to globally handle for this, courtesy of Andy Bell's <a href="https://piccalil.li/blog/a-modern-css-reset">Modern CSS Reset</a>.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Remove all animations and transitions for people that prefer not to see them */</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">*, *::before, *::after</span> <span class="token punctuation">{</span> <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">scroll-behavior</span><span class="token punctuation">:</span> auto <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Animations are ran once, and any transitions will happen instantly. In some instances, you'll want to plan animations for this possibility to ensure they &quot;freeze&quot; on the desirable frame.</p> <p>This demo includes a gently pulsing circle unless a user has selected reduced motion.</p> <details false=""> <summary>CSS for "Demo of `prefers-reduced-motion`"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @keyframes pulse { 0% { transform: scale(0.85); } 100% { transform: scale(1.25); } } .prefers-reduced-motion-807 { width: 3rem; height: 3rem; border-radius: 50%; border: 3px solid purple; animation: pulse 2000ms infinite alternate ease-in-out; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="prefers-reduced-motion-807"></div> </div> </div> <p>You can test results of this media feature query within Chrome/Edge by opening dev tools and selecting the kebab menu (3 vertical dots), then &quot;More tools&quot; and &quot;Rendering&quot;. Then you can toggle settings in the section for &quot;Emulate CSS media feature prefers-reduced motion&quot;.</p> <img src="https://moderncss.dev/img/posts/27/chrome-prefers-reduced-motion.png" alt="preview of the dev tools panel for this setting as described in the previous text" width="400" /> <div class="heading-wrapper h2"> <h2 id="respecting-color-and-contrast-settings">Respecting Color and Contrast Settings</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#respecting-color-and-contrast-settings" aria-labelledby="respecting-color-and-contrast-settings"><span hidden="">#</span></a></div> <p>Dark mode seems to be a fad, but for some users it's essential for ensuring they can read your content. While there are currently no guidelines instructing that both a dark and light mode for content is required, you can begin to be future-friendly by considering offering both modes.</p> <p>What <em>is</em> a requirement is that if you do offer dark mode that is continues to pass at least the standard color contrast guidelines available in <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum">Success Criterion 1.4.3: Contrast (Minimum)</a>.</p> <p>The minimum contrast ratios to meet are at least:</p> <ul> <li>4.5:1 for normal text</li> <li>3:1 for large text - defined as 18.66px <strong>and</strong> bold or larger, or 24px and larger</li> <li>3:1 for graphics and user interface components (such as form input borders)</li> </ul> <blockquote> <p><strong>Head's up</strong>: A new color contrast model is being considered for WCAG 3, but it will be a few years before it becomes the standard. You can learn more about the proposed Advanced Perceptual Contrast Algorithm (APCA) in the <a href="https://w3c.github.io/silver/guidelines/methods/Method-font-characteristic-contrast.html">WCAG 3 draft guidelines</a> (these may change over time until it is standard).</p> </blockquote> <p>Whether or not you provide dark and light mode, users of Windows 10 devices can select a system setting to enable <a href="https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696">High Contrast Mode</a>.</p> <p>In most cases, you should allow the user's settings in this mode to be applied. Occasionally, the application of the high contrast settings means colors that are important to understanding your interface will be removed.</p> <p>Three often critical properties that will usually be removed in Windows High Contrast Mode:</p> <ul> <li><code>box-shadow</code></li> <li><code>background-color</code></li> <li><code>background-image</code> unless it contains a <code>url()</code> value</li> </ul> <p>And two critical properties that will have their color swapped for the &quot;system color&quot; equivalent:</p> <ul> <li><code>color</code></li> <li><code>border-color</code></li> </ul> <blockquote> <p>You can <a href="https://www.w3.org/TR/css-color-adjust-1/#forced-colors-properties">review a full list of properties</a> affected by this &quot;forced color&quot; mode.</p> </blockquote> <p>Sometimes these properties can be compensated by using transparent alternates. For example, if you are using <code>box-shadow</code> as an alternate to <code>outline</code> in order to match <code>border-radius</code> on <code>:focus</code>, you should still include an <code>outline</code> with the color value set to <code>transparent</code> as the complement to retain a <code>:focus</code> style for forced color modes. This example is included in my <a href="https://moderncss.dev/css-button-styling-guide/">CSS button styling guide</a>. In a similar way, you can include a transparent border in place of <code>box-shadow</code> if the shadow was intended to communicate an important boundary.</p> <p>If you're using SVG icons, passing <code>currentColor</code> as the value of <code>fill</code> or <code>stroke</code> will help ensure they respond to the forced color settings.</p> <p>Or, you can use a special media feature query to assign colors from the <a href="https://www.w3.org/TR/css-color-4/#typedef-system-color">system color palette</a>. These respect user settings while allowing you to ensure your critical interface elements retain color.</p> <p>Here's an example of using simple CSS shapes and gradients as icons, and ensuring they retain color within a forced color mode. Within the <code>forced-colors</code> feature query, we only need to set the <code>color</code> property because we've set up the icons to use the <code>currentColor</code> value.</p> <details false=""> <summary>CSS for "Demo of forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.icon</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.filled</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gradient</span> <span class="token punctuation">{</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span> 45deg<span class="token punctuation">,</span> currentColor<span class="token punctuation">,</span> currentColor 2px<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 2px<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 6px <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">forced-colors</span><span class="token punctuation">:</span> active<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.icon</span> <span class="token punctuation">{</span> // Required to enable colors <span class="token property">forced-color-adjust</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> // User-preferred &amp;quot<span class="token punctuation">;</span>text&amp;quot<span class="token punctuation">;</span> color <span class="token property">color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .icon-489 { width: 1.5rem; height: 1.5rem; display: inline-block; color: blue; } .filled-489 { background-color: currentColor; border-radius: 50%; } .gradient-489 { background-image: repeating-linear-gradient( 45deg, currentColor, currentColor 2px, rgba(255, 255, 255, 0) 2px, rgba(255, 255, 255, 0) 6px ); border: 1px solid; } @media screen and (forced-colors: active) { .icon-489 { // Required to enable colors forced-color-adjust: none; // User-preferred "text" color color: CanvasText; } } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><span class="icon-489 filled-489"></span> <span class="icon-489 gradient-489"></span></p> </div> </div> <p>Using Windows High Contrast Mode, a likely outcome based on defaults would be that the blue becomes white due to the <code>CanvasText</code> color keyword while the page background becomes black.</p> <blockquote> <p>Review more about how this mode works with a more extensive example on the <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">Microsoft Edge Blog</a></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="accessibility-learning-resources">Accessibility Learning Resources</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#accessibility-learning-resources" aria-labelledby="accessibility-learning-resources"><span hidden="">#</span></a></div> <p>While we linked to success criteria and other resources throughout the examples, there is a lot more to learn!</p> <p>Here are some additional resources to learn more:</p> <ul> <li><a href="https://a11y.coffee/">a11y.coffee</a></li> <li><a href="https://solidstart.info/">solidstart.info</a></li> <li>This extensive guide about <a href="https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/">accessible front-end components from Smashing Magazine</a></li> <li>Search and explore the <a href="https://www.getstark.co/library">library of resources available from Stark</a></li> </ul> <p>I've created a variety of resources as well:</p> <ul> <li>An <a href="https://dev.to/5t3ph/introduction-to-web-accessibility-5cmp">intro to accessibility</a> from my beginner's webdev series</li> <li>Practice resolving issues with my <a href="https://github.com/5t3ph/a11y-fails">a11y fails project</a></li> <li>Listen to the two part Word Wrap podcast series (that I co-host) on <a href="https://wordwrap.dev/episodes/008/">common accessibility failures</a> and <a href="https://wordwrap.dev/episodes/009/">WCAG success criteria you may not be meeting</a></li> <li>Learn about handling stateful button contrast and generate an accessible palette with my web app <a href="https://buttonbuddy.dev/">ButtonBuddy</a></li> <li>Use the <a href="https://www.npmjs.com/package/a11y-color-tokens">a11y-color-tokens package</a> to speed up generating an accessible color palette</li> </ul> <blockquote> <p>I enjoy talking about accessibility and do my best to design accessible tutorials and callout any accessibility specifics. If you spot an error or want to suggest an improvement, <a href="https://thinkdobecreate.com/contact/">contact me</a>.</p> </blockquote> Developing For Imperfect: Future Proofing CSS Styles 2021-03-28T00:00:00Z https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/ <p>How do we plan future-proof styles in a world with an infinite degree of device and user ability variance? Let's explore how things can break and how modern CSS provides solutions.</p> <p>This episode will cover handling for:</p> <ul> <li>variable content length and overflow</li> <li>unpredictable media sizes</li> <li>internationalization</li> <li>accessibility-related user settings</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>We'll explore creating a robust comment thread component. Here's our starting point - an exact mimic of the layout you received from design, good job!</p> <details> <summary>CSS for "Initial Comment Thread"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment-list</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment .comment-list</span> <span class="token punctuation">{</span> <span class="token property">grid-column-start</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span> <span class="token property">grid-column-end</span><span class="token punctuation">:</span> -1<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 64px 1fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-body</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> .5rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #444<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-meta</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #767676<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> .875rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-body a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-meta a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> mediumvioletred<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-883 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-883 .comment-list-883 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-883 { display: grid; grid-template-columns: 64px 1fr; gap: 1rem; } .comment-body-883 { display: grid; gap: .5rem; color: #444; } .comment-meta-883 { color: #767676; font-size: .875rem; } .comment-body-883 a { color: inherit; } .comment-meta-883 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-883"> <li class="comment-883"> <span class="comment-avatar"><img alt="@baywriter avatar" src="https://moderncss.dev/img/posts/26/avatar1.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter</a> <em>15 mins ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> <li class="comment-883"> <span class="comment-avatar"><img alt="@michelle avatar" src="https://moderncss.dev/img/posts/26/avatar2.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle</a> <em>1 hour ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> <ul class="comment-list-883"> <li class="comment-883"> <span class="comment-avatar"><img alt="@claudia87 avatar" src="https://moderncss.dev/img/posts/26/avatar3.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>But if you resize it, you'll already notice a few problems, particularly with overflow.</p> <p><small><em>Avatar illustrations are part of the <a href="https://blush.design/collections/women-power">Women Power</a> collection on Blush by Sara Pelaez</em>.</small></p> <div class="heading-wrapper h2"> <h2 id="responsive-planning">Responsive Planning</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#responsive-planning" aria-labelledby="responsive-planning"><span hidden="">#</span></a></div> <p>When you can't precisely plan around content, plan for flexibility. Rather than setting absolutes, we can use CSS functions to choose the best value relative to the current context.</p> <p>In our <code>.comment</code> styles, we set a precise pixel value for the avatar. Instead, we can use the CSS function <code>min</code> to select the <em>minimum computed value</em> between a list of options.</p> <details open=""> <summary>CSS for "Updated Avatar Grid Column"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.comment</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 64px 1fr<span class="token punctuation">;</span></del> <ins class="highlight-line highlight-line-add"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></ins> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .comment-list-403 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-403 .comment-list-403 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-403 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-403 { display: grid; gap: .5rem; color: #444; } .comment-meta-403 { color: #767676; font-size: .875rem; } .comment-body-403 a { color: inherit; } .comment-meta-403 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-403"> <li class="comment-403"> <span class="comment-avatar"><img alt="@baywriter avatar" src="https://moderncss.dev/img/posts/26/avatar1.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter</a> <em>15 mins ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> <li class="comment-403"> <span class="comment-avatar"><img alt="@michelle avatar" src="https://moderncss.dev/img/posts/26/avatar2.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle</a> <em>1 hour ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> <ul class="comment-list-403"> <li class="comment-403"> <span class="comment-avatar"><img alt="@claudia87 avatar" src="https://moderncss.dev/img/posts/26/avatar3.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>In this case, the impact is that the avatar will never exceed <code>64px</code> for larger viewports. And for smaller viewports <em>or</em> within narrower containers, it will be computed as <code>15%</code> of the total comment width.</p> <p>As this example shows, sometimes we can turn over layout choices to the browser to make contextually versus define precise values within media queries.</p> <div class="heading-wrapper h2"> <h2 id="always-expect-more">Always Expect More</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#always-expect-more" aria-labelledby="always-expect-more"><span hidden="">#</span></a></div> <p>Whether characters or elements, always expect more than the original design has planned.</p> <p>Our avatar update has already improved things. But we're still viewing the component with &quot;happy path&quot; content from our designer that doesn't reflect real-world data. Notably, the user names and comment lengths are relatively short.</p> <p>Let's update our demo data to have a bit more variance and &quot;real&quot; avatars:</p> <style> .comment-list-754 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-754 .comment-list-754 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-754 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-754 { display: grid; gap: .5rem; color: #444; } .comment-meta-754 { color: #767676; font-size: .875rem; } .comment-body-754 a { color: inherit; } .comment-meta-754 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-754"> <li class="comment-754"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-754"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-754"> <li class="comment-754"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Not as pretty as our mockup mimic 😊</p> <p>Now, if you've been following along and testing our component via resizing, you'll see we have the possibility of content overflow.</p> <p>Let's resolve it first for the <code>.comment-meta</code>, which is the <code>small</code> tag containing the username and date.</p> <p>We will update the layout method to allow the username and date to line up on wider viewports and stack on smaller viewports. Simple flex behavior allows this since child elements will be their max-width when there's room and flow to a new row when the containing element reduces below that max-width.</p> <details open=""> <summary>CSS for "Update Comment Meta Layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment-meta</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-209 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-209 .comment-list-209 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-209 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-209 { display: grid; gap: .5rem; color: #444; } .comment-meta-209 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-209 a { color: inherit; } .comment-meta-209 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-209"> <li class="comment-209"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-209"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-209"> <li class="comment-209"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>While <a href="https://caniuse.com/flexbox-gap">flexbox <code>gap</code> support</a> is gaining, in this case, the degraded behavior is simply very slightly closer elements, so it isn't too detrimental not to provide a fallback.</p> <p>Go ahead and test this version and see how the dates bump to a new line when there isn't enough space for their full width.</p> <div class="heading-wrapper h3"> <h3 id="preventing-content-overflow">Preventing Content Overflow</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#preventing-content-overflow" aria-labelledby="preventing-content-overflow"><span hidden="">#</span></a></div> <p>In the demo data, the longer email in the second comment eventually causes overflow scroll on smaller viewports. So does the extended URL in the comment body.</p> <p>The fix could be scoped to only the link elements if we'd like. However, due to the nature of a comment thread, it seems to make sense to be extra-preventative about overflow content in this context So we'll apply two properties to the top-level <code>.comment</code> in order to cascade to all the content within.</p> <details open=""> <summary>CSS for "Preventing Content Overflow"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token comment">/* Help prevent overflow of long words/names/URLs */</span> <span class="token property">overflow-wrap</span><span class="token punctuation">:</span> break-word<span class="token punctuation">;</span> <span class="token comment">/* Optional, not supported for all languages */</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment a</span> <span class="token punctuation">{</span> <span class="token comment">/* Remove from links to prevent perceiving false hyphens */</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-378 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-378 .comment-list-378 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-378 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-378 { display: grid; gap: .5rem; color: #444; } .comment-meta-378 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-378 a { color: inherit; hyphens: none; } .comment-meta-378 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-378"> <li class="comment-378"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-378"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-378"> <li class="comment-378"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Notice we removed the possibility of <code>hyphens</code> from links. In this case, the full links are visible like in our example, and someone tries to write it down or read it aloud.</p> <blockquote> <p>CSS-inserted hyphens are not included if a user copies the text. As noted, <code>hyphens</code> are also not consistently available for all languages in all browsers. You can <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#browser_compatibility">review <code>hyphens</code> support on MDN</a>.</p> </blockquote> <p>With <code>overflow-wrap: break-word</code>, any text string <em>may</em> be broken onto a new line once the containing element doesn't have room for its full-width. When <code>hyphens</code> are supported, the bonus effect is reducing a &quot;ragged edge&quot; from odd spaces caused by broken words.</p> <p>Optionally, you may want to update links to use <code>overflow-wrap: anywhere;</code> to prevent an empty space if the browser decides to move the link to a new line before applying the break. You can see our current solution on smaller viewports currently leaves a space before the long exposed link.</p> <p>Try out resizing the component now, and perhaps even pop open dev tools to inspect and toggle on and off these properties to see the difference in their effects.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="handling-variable-media-dimensions">Handling Variable Media Dimensions</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#handling-variable-media-dimensions" aria-labelledby="handling-variable-media-dimensions"><span hidden="">#</span></a></div> <p>Now let's deal with those avatars.</p> <p>First, we set <code>border-radius</code> to create a circle appearance. Then we ensure the image fills the grid column with <code>width: 100%</code>. Following that, we turn the image into its own container and allow the image content to fill but not exceed the <code>img</code> dimensions with <code>object-fit: cover</code>. We end the rule with a <a href="https://caniuse.com/mdn-css_properties_aspect-ratio">cutting-edge property of <code>aspect-ratio</code></a> to ensure a perfect square.</p> <details open=""> <summary>CSS for "Updated Avatar Dimensions"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment img</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.comment-avatar</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-avatar img</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-890 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-890 .comment-list-890 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-890 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-890 { display: grid; gap: .5rem; color: #444; } .comment-meta-890 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-890 a { color: inherit; hyphens: none; } .comment-meta-890 a { color: mediumvioletred; } .comment-avatar-890 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-890 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-890 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-890"> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-890"> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>We follow that rule with a feature detection fallback rule - <code>@supports not (aspect-ratio: 1)</code> - for browsers that do <em>not</em> support <code>aspect-ratio</code>. This fallback is an older technique that relies on padding to ensure a perfect square ratio of the image's parent and then ensures the <code>img</code> fills that area.</p> <blockquote> <p>Previous Modern CSS tutorials have covered <code>object-fit</code>, such as <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">CSS-Only Full-Width Responsive Images 2 Ways</a>. You may also enjoy this <a href="https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit">3 min video demonstrating <code>object-fit</code></a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="breaking-behavior-to-test">Breaking Behavior To Test</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#breaking-behavior-to-test" aria-labelledby="breaking-behavior-to-test"><span hidden="">#</span></a></div> <p>We've covered the scenarios we could detect fairly easily by resizing our browser/the component container. And adding more real-world data helped us define better avatar styles.</p> <p>There are a few more items we need to explicitly test for: internationalization (i18n) and a few relevant WCAG success criteria for accessibility.</p> <blockquote> <p><strong>Terminology check</strong>: WCAG stands for the &quot;<a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines</a>,&quot; a set of standards intended to help create more accessible and inclusive experiences. <em><a href="https://www.w3.org/WAI/WCAG22/Understanding/understanding-techniques">Success Criteria</a></em> are guidance that is broadly applicable to current and future web technologies in order to assist in creating experiences that are accessible.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="internationalization-i18n">Internationalization (i18n)</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#internationalization-i18n" aria-labelledby="internationalization-i18n"><span hidden="">#</span></a></div> <p>Yes, the comments are silly nonsense (courtesy of <a href="http://www.cupcakeipsum.com/">cupcake ipsum</a> and <a href="https://www.cipsum.com/">corporate ipsum</a>). However, for something like a comment thread component that's purpose is to intake and display user-submitted content, it's a great idea to stress-test by trialing some translations.</p> <p>The first comment is German, the second is Estonian, and the third is Arabic.</p> <details open=""> <summary>CSS for "RTL Text Styling"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-732 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-732 .comment-list-732 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-732 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; text-align: start; } .comment-body-732 { display: grid; gap: .5rem; color: #444; } .comment-meta-732 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-732 a { color: inherit; hyphens: none; } .comment-meta-732 a { color: mediumvioletred; } .comment-avatar-732 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-732 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-732 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-732"> <li lang="de" class="comment-732"> <span class="comment-avatar-732"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>Vor 15 Minuten</em></small> <p>Podcasting des operativen Änderungsmanagements innerhalb von Workflows, um ein Framework zu erstellen. Schalten Sie nahtlose Leistungsindikatoren offline, um den Long Tail zu maximieren.</p> </div> </li> <li lang="et" class="comment-732"> <span class="comment-avatar-732"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 tund tagasi</em></small> <p>Kasutage agaraid raamistikke, et pakkuda põhjalikku ülevaadet kõrgetasemeliste ülevaadete jaoks. Iteratiivsed lähenemised ettevõtte strateegiale soodustavad koostöömõtlemist, et edendada üldist väärtuspakkumist.</p> <p>Kasvata orgaaniliselt terviklikku maailmavaadet häirivast innovatsioonist töökoha mitmekesisuse ja mõjuvõimu suurendamise kaudu.</p> </div> <ul class="comment-list-732"> <li lang="ar" dir="rtl" class="comment-732"> <span class="comment-avatar-732"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>31 أكتوبر 2021 الساعة 6:54 مساءً</em></small> <p>سيؤدي الانغماس في تقنية النانو على طول طريق المعلومات السريع إلى إغلاق الحلقة حول التركيز فقط على المحصلة النهائية</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Thanks to our previous work on handling overflow, our comment thread is gracefully handling the change in content languages.</p> <p>On the third one that is in Arabic, the browser is handling the content direction switch firstly due to placing the attribute <code>dir=&quot;rtl&quot;</code> on the <code>.comment</code> list element. Interestingly, the browser intelligently switches the order of the <code>grid-template-columns</code> without our needing to do anything extra. Flexbox will also flip according to this attribute. Older styles that use floats would not flip and would require an additional override.</p> <p>We've defined just one extra property: <code>text-align: start</code>. This is called a <em><a href="https://rtlstyling.com/posts/rtl-styling#css-logical-properties">logical property</a></em> and in the case of RTL being defined it flips the text and becomes equivalent to <code>text-align: right</code>. While <a href="https://caniuse.com/css-logical-props">support is still gaining for logical properties</a>, you may need to include a fallback. Since we're using <code>gap</code> for spacing throughout, no update is needed there. If we were using margins that were affected, we could again use logical properties to help do the conversion when needed.</p> <p>Since I am not an RTL (right-to-left) styling expert, I will point you to this fantastic resource if you would like to <a href="https://rtlstyling.com/posts/rtl-styling/">learn more about RTL styling</a>.</p> <div class="heading-wrapper h3"> <h3 id="wcag-success-criterion-1410-reflow">WCAG Success Criterion 1.4.10 - Reflow</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410-reflow" aria-labelledby="wcag-success-criterion-1410-reflow"><span hidden="">#</span></a></div> <p>Reflow is the term for supporting desktop zoom up to 400%. On a 1280px wide resolution at 400%, the viewport content is equivalent to 320 CSS pixels wide.</p> <p>Zooming on a desktop eventually triggers what we usually think of as &quot;responsive design&quot; behavior. In fact, if you are using media queries or other viewport-based layout methods, you will see those begin to take hold as you zoom in.</p> <p>The trouble with handling this success criterion is usually two-fold:</p> <ul> <li>there is no <code>zoom</code> media query to adjust for any issues</li> <li>the aspect ratio of a desktop using zoom is different than the mobile portrait mode we usually plan responsive design around</li> </ul> <p>The aspect ratio difference, in particular, can cause issues with overlap. It also means solutions that rely on only viewport units or percentages appear either too large or too small within a zoom context.</p> <p>However, viewport units used in combination with other units can actually help solve zoomed layout issues as well and gap-fill the problem of not having a dedicated <code>zoom</code> media query.</p> <p>If we zoom our component to 400%, the avatar column begins to grow a bit large within that context. We'd like it to take up a relatively similar size column as we perceive it at standard zoom.</p> <p>Recall that we originally applied <code>min</code> to the avatar's grid column, which was intended to resize the avatar for smaller containers and viewports via a percentage width. Fortunately, the <code>min</code> function can take more than two values!</p> <p>Now, this type of fix can take some trial and error, but to my eye, adding <code>10vw</code> as an additional value provided the desired adjustment. It also slightly reduced the avatar for true mobile viewports but was a worthwhile trade-off.</p> <p>The benefit of retaining the percentage width is that our component continues to be responsive to its parent container as well. If we removed it, we would not see a reduction until the viewport units began to take effect, which may objectively be too late.</p> <details open=""> <summary>CSS for "Update Column Minimum Allowed Widths"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.comment</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></del> <ins class="highlight-line highlight-line-add"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">,</span> 10vw<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></ins> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .comment-list-381 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-381 .comment-list-381 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-381 { display: grid; grid-template-columns: min(64px, 15%, 10vw) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-381 { display: grid; gap: .5rem; color: #444; } .comment-meta-381 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-381 a { color: inherit; hyphens: none; } .comment-meta-381 a { color: mediumvioletred; } .comment-avatar-381 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-381 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-381 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-381"> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-381"> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>If you are using a desktop browser, bump up your zoom to 400%. Then open dev tools (recommended not to place it as a sidebar for this test) and find this demo. Select one of the <code>.comment</code> list items and adjust the <code>10vw</code> by using your arrow keys to see when it &quot;wins&quot; versus the other values. Then, swap to a mobile emulator and view whether your adjustment positively or negatively impacted the avatar in that view.</p> <p>Resolving zoom layout issues takes some patience, but <code>min</code> is one of the best modern CSS tools I've found to assist with this challenge.</p> <div class="heading-wrapper h3"> <h3 id="wcag-success-criterion-1412-text-spacing">WCAG Success Criterion 1.4.12 - Text Spacing</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1412-text-spacing" aria-labelledby="wcag-success-criterion-1412-text-spacing"><span hidden="">#</span></a></div> <p>Another criterion you may not be aware of or already be testing for is <em>Text Spacing</em>.</p> <p>According to the <a href="https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html">text spacing understanding documentation</a>, your content should be flexible for user settings, including:</p> <ul> <li>Line height (line spacing) to at least 1.5 times the font size</li> <li>Spacing following paragraphs to at least 2 times the font size</li> <li>Letter spacing (tracking) to at least 0.12 times the font size</li> <li>Word spacing to at least 0.16 times the font size</li> </ul> <p>Luckily, there is a <a href="https://dylanb.github.io/bookmarklets.html">text spacing bookmarklet</a> you can grab that will apply styles to test this criterion.</p> <p>If you're unfamiliar with a bookmarklet, you can click the link and drag it to your bookmark bar. In this case, the bookmarklet contains a tiny script that will apply the text spacing styles to all elements on the page you're viewing to allow you to test this criterion.</p> <p>Applying the bookmarklet test to our comment thread component, we, fortunately, encounter no issues thanks to our previous efforts.</p> <blockquote> <p>You may have difficulties with this criterion if you try to define content boxes with absolute dimensions, or rely on CSS truncation methods, or force inline dimension styles with JavaScript. If the content is cut-off or overflows, you need to resolve it to meet this criterion.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="final-demo">Final Demo</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#final-demo" aria-labelledby="final-demo"><span hidden="">#</span></a></div> <form action="https://codepen.io/pen/define" method="POST" target="_blank"> <input type="hidden" name="data" value='{"title":"Modern CSS Solutions - Comment Thread Component","description":"Generated from: ModernCSS.dev/developing-for-imperfect-future-proofing-css-styles/","tags":["moderncss"],"editors":"110","layout":"left","html":"<!-- Modern CSS Solutions - Comment Thread Component\nGenerated from: ModernCSS.dev/developing-for-imperfect-future-proofing-css-styles/ -->\n<ul class=\"comment-list\">\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@baywriter_cactusmom avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr1.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@baywriter_cactusmom</a> <em>15 mins ago</em></small>\n <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p>\n </div>\n </li>\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@michelle_n_catz@superremail.co.uk avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr2.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small>\n <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n <p>Have you seen <a href=\"https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/\">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p>\n </div>\n <ul class=\"comment-list\">\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@claudia87_author avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr3.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small>\n <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p>\n <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p>\n </div>\n </li>\n </ul>\n </li>\n</ul>\n","html_pre_processor":"none","css":"/* Box sizing rules */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* Remove default margin */\nbody,\nh1,\nh2,\nh3,\nh4,\np {\n margin: 0;\n}\n\n/* Set core body defaults */\nbody {\n min-height: 100vh;\n text-rendering: optimizeSpeed;\n line-height: 1.5;\n font-family: system-ui, sans-serif;\n}\n\n/* Make images easier to work with */\nimg {\n display: block;\n max-width: 100%;\n}\n\n/***\n 🟣 Modern CSS Solutions Demo Styles\n */\n\n.comment-list {\n list-style: none;\n padding: 0.5rem;\n margin: 0;\n display: grid;\n gap: 1.5rem;\n}\n\n.comment .comment-list {\n grid-column-start: 2;\n grid-column-end: -1;\n padding: 0;\n}\n\n.comment {\n display: grid;\n grid-template-columns: min(64px, 15%, 10vw) 1fr;\n gap: 1rem;\n overflow-wrap: break-word;\n hyphens: auto;\n}\n\n.comment-body {\n display: grid;\n gap: .5rem;\n color: #444;\n}\n\n.comment-meta {\n color: #767676;\n font-size: .875rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.comment-body a {\n color: inherit;\n hyphens: none;\n}\n\n.comment-meta a {\n color: mediumvioletred;\n}\n\n.comment-avatar img {\n border-radius: 50%;\n width: 100%;\n object-fit: cover;\n aspect-ratio: 1;\n}\n\n@supports not (aspect-ratio: 1) {\n .comment-avatar {\n position: relative;\n height: 0;\n padding-bottom: 100%;\n }\n\n .comment-avatar img {\n position: absolute;\n height: 100%;\n }\n}\n","css_pre_processor":"scss","css_starter":"neither","css_prefix":"autoprefixer","head":"<meta name=&apos;viewport&apos; content=&apos;width=device-width, initial-scale=1&apos;>"}' /> <button class="button" type="submit" data-name="Comment Thread Component" aria-label="Open Comment Thread Component in CodePen"><span class="button__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false"> <path d="M32 10.909l-0.024-0.116-0.023-0.067c-0.013-0.032-0.024-0.067-0.040-0.1-0.004-0.024-0.020-0.045-0.027-0.067l-0.047-0.089-0.040-0.067-0.059-0.080-0.061-0.060-0.080-0.060-0.061-0.040-0.080-0.059-0.059-0.053-0.020-0.027-14.607-9.772c-0.463-0.309-1.061-0.309-1.523 0l-14.805 9.883-0.051 0.053-0.067 0.075-0.049 0.060-0.067 0.080c-0.027 0.023-0.040 0.040-0.040 0.061l-0.067 0.080-0.027 0.080c-0.027 0.013-0.027 0.053-0.040 0.093l-0.013 0.067c-0.025 0.041-0.025 0.081-0.025 0.121v9.996c0 0.059 0.004 0.12 0.013 0.18l0.013 0.061c0.007 0.040 0.013 0.080 0.027 0.115l0.020 0.067c0.013 0.036 0.021 0.071 0.036 0.1l0.029 0.067c0 0.013 0.020 0.053 0.040 0.080l0.040 0.053c0.020 0.013 0.040 0.053 0.060 0.080l0.040 0.053 0.053 0.053c0.013 0.017 0.013 0.040 0.040 0.040l0.080 0.056 0.053 0.040 0.013 0.019 14.627 9.773c0.219 0.16 0.5 0.217 0.76 0.217s0.52-0.080 0.76-0.24l14.877-9.875 0.069-0.077 0.044-0.060 0.053-0.080 0.040-0.067 0.040-0.093 0.021-0.069 0.040-0.103 0.020-0.060 0.040-0.107v-10c0-0.067 0-0.127-0.021-0.187l-0.019-0.060 0.059 0.004zM16.013 19.283l-4.867-3.253 4.867-3.256 4.867 3.253-4.867 3.253zM14.635 10.384l-5.964 3.987-4.817-3.221 10.781-7.187v6.424zM6.195 16.028l-3.443 2.307v-4.601l3.443 2.301zM8.671 17.695l5.964 3.987v6.427l-10.781-7.188 4.824-3.223v-0.005zM17.387 21.681l5.965-3.973 4.817 3.227-10.783 7.187v-6.427zM25.827 16.041l3.444-2.293v4.608l-3.444-2.307zM23.353 14.388l-5.964-3.988v-6.44l10.78 7.187-4.816 3.224z"></path> </svg></span> Open in CodePen</button> </form> <p>You may choose the &quot;Open in CodePen&quot; option to generate a new CodePen that includes the final styles created for this component. Use it as an opportunity to better explore the various updates we applied, including:</p> <ul> <li>responsive grid column sizing with <code>min</code></li> <li>flexbox for variable width content reflow</li> <li><code>overflow-wrap</code> and <code>hyphens</code> to handle content overflow</li> <li>combining units within <code>min</code> to account for various viewport sizes as well as zoom</li> </ul> Guide to Advanced CSS Selectors - Part Two 2020-12-30T00:00:00Z https://moderncss.dev/guide-to-advanced-css-selectors-part-two/ <p><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/">Continuing from part one</a>, this episode will focus on the advanced CSS selectors categorized as pseudo classes and pseudo elements and practical applications for each. We'll especially try to make sense of the syntax for <code>nth-child</code>.</p> <div class="heading-wrapper h2"> <h2 id="part-two-this-article">Part Two (this article):</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#part-two-this-article" aria-labelledby="part-two-this-article"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes">Pseudo classes</a> - ex: <code>:checked / :focus</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements">Pseudo elements</a> - ex: <code>::before / ::after</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources">Additional resources</a></li> </ul> <div class="heading-wrapper h3"> <h3 id="part-one">Part One:</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#part-one" aria-labelledby="part-one"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade">CSS Specificity and the Cascade</a></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector">Universal selector</a> - <code>*</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector">Attribute selector</a> - <code>[attribute]</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator">Child combinator</a> - <code>&gt;</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator">General sibling combinator</a> - <code>~</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">Adjacent sibling combinator</a> - <code>+</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="pseudo-classes">Pseudo Classes</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes" aria-labelledby="pseudo-classes"><span hidden="">#</span></a></div> <p>This is the largest category, and also the most context-dependent.</p> <p>Pseudo classes are keywords that are applied when they match the selected state or context of an element.</p> <p>These vastly increase the capabilities of CSS and enable functionality that in the past was often erroneously relegated to JavaScript.</p> <p>Some selectors are stateful:</p> <ul> <li><code>:focus</code></li> <li><code>:hover</code></li> <li><code>:visited</code></li> <li><code>:target</code></li> <li><code>:checked</code></li> </ul> <p>While others attach to the order of elements:</p> <ul> <li><code>:nth-child()</code> / <code>:nth-of-type()</code></li> <li><code>:first-child</code> / <code>:first-of-type</code></li> <li><code>:last-child</code> / <code>:last-of-type</code></li> <li><code>:only-child</code> / <code>:only-of-type</code></li> </ul> <p>Then there's the highly useful pseudo class <code>:not()</code>, the newly supported <code>:is()</code>, as well as the <code>:root</code> pseudo class that has come to the forefront as CSS custom properties (variables) have risen in support.</p> <blockquote> <p>Review the MDN docs for the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes">full list of available pseudo classes</a> including available options specific to form inputs, <code>video</code> captions, and language, as well as some currently in progress towards implementation.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-pseudo-classes">Practical Applications For Pseudo Classes</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#practical-applications-for-pseudo-classes" aria-labelledby="practical-applications-for-pseudo-classes"><span hidden="">#</span></a></div> <h4>Zebra Striped Table Rows</h4> <p>The <code>nth</code> series of selectors has endless applications. Use them for anything you want to occur in any sort of repetitive pattern using a 1-based index.</p> <p>An excellent candidate for this is zebra striping of table rows.</p> <p>The <code>nth-child</code> selector can use an integer, be defined using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child#Functional_notation">functional notation</a>, or the keywords of <code>even</code> or <code>odd</code>. We'll use the keywords to most efficiently produce our zebra striping rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">tbody tr:nth-child(odd)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #ddd<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which will produce the following:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wjr035afx1qgkanwnuxe.png" alt="a two-column table where rows in the table body have a light grey background for every other (odd) table row background" /></p> <h4>Apply Alternating Background Colors</h4> <p>Using the functional notation for <code>nth-child</code> we can alternate through a series of background colors and ensure the pattern repeats in the defined order no matter how many elements exist. So a pattern of <code>rebeccapurple</code>, <code>darkcyan</code>, <code>lightskyblue</code> will repeat in that order.</p> <p>The way this works is to define the total number of colors - <code>3</code> - alongside <code>n</code>, which represents all positive numbers starting from 0, and which will be multiplied by the associated number, in this case, <code>3</code>. So by itself, <code>3n</code> would select the 3rd item, the 6th item, the 9th item, and so on. It would not select the first in the list because <code>3 x 0 = 0</code>.</p> <p>For our repetitive pattern, we want the first item selected to be the first color in our palette.</p> <p>So, we extend the notation to be <code>3n + (integer corresponding to color order)</code>, therefore our first color rule becomes:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li:nth-child(3n + 1)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> rebeccapurple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And this selects every third element, starting with the first one:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/a2smgktrsc6b7o182irc.png" alt="5 stacked items where the first and fourth have the background-color: rebeccapurple applied" /></p> <p>Essentially, that <code>+ [number]</code> shifts the starting index.</p> <p>To complete our pattern we define the following rules, incrementing the added number to be the order of the color in the repeating pattern:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li:nth-child(3n + 2)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> darkcyan<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li:nth-child(3n + 3)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> lightskyblue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Producing the following completed result:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNMpBjY" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>For an extended guide to <code>nth-child</code> checkout the <a href="https://css-tricks.com/useful-nth-child-recipies/">recipe reference</a> from CSS-Tricks and the <a href="https://css-tricks.com/examples/nth-child-tester/">nth-child tester</a> to explore constructing these selectors.</p> <blockquote> <p><strong>Enjoying this guide and finding some useful solutions</strong>? I'd appreciate a coffee to help keep me motivated to create more resources! I also offer front-end reviews and mentoring sessions, <a href="https://www.buymeacoffee.com/moderncss">choose an option to support me</a>.</p> </blockquote> <h4>Removing Extra Spacing From Child Elements</h4> <p>If you are not using a reset that starts all elements with zero margin, you may encounter typography elements creating extra unwanted margins that unbalance spacing within a visual container.</p> <p>Pseudo classes do not always need to be directly attached to an element, meaning we can do the following rule which attaches to <em>any</em> element that happens to be the last child of any parent and ensures it has no <code>margin-bottom</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">:last-child</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Excluding Elements From Selectors</h4> <p>Careful application of <code>:not()</code> is very useful for excluding elements from being selected.</p> <p>We explored a few uses of <code>:not()</code> within the attribute selector section, notably <code>a:not([class])</code> for targeting links that have no other classes applied.</p> <p><code>:not()</code> is excellent for use in utility frameworks or design systems to increase specificity on classes that have the potential to be applied to anything and for which there are known issues on certain combinations.</p> <p>An extended example of excluding it for classes with links is when you are adjusting contrast for text, possibly in a dark mode context, and want to apply the contrast adjustment to text links as well:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Non dark mode application */</span> <span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Update text color for dark mode */</span> <span class="token selector">.dark-mode</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Extend the color update to links via `inherit` */</span> <span class="token selector">.dark-mode a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can also chain <code>:not()</code> selectors, so perhaps you want to create a rule for form field inputs, but not of certain types:</p> <pre class="language-css"><code class="language-css"><span class="token property">input</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"hidden"</span>]<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"radio"</span>]<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"checkbox"</span>]<span class="token punctuation">)</span></code></pre> <p>It's also possible to include other pseudo selectors within <code>:not()</code> such as to exclude the <code>:disabled</code> state for buttons:</p> <pre class="language-css"><code class="language-css"><span class="token property">button</span><span class="token punctuation">:</span> <span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span> disabled<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>This allows you to have tidier rules by defining a reset of <code>button</code> styles first, then <em>only</em> apply coloration styles, borders, etc to non-disabled buttons instead of <em>removing</em> those styles for <code>button:disabled</code> later.</p> <h4>Efficiently Select Groups of Elements</h4> <p>The <a href="https://caniuse.com/css-matches-pseudo">newly supported</a> <code>:is()</code> pseudo class:</p> <blockquote> <p>&quot;...takes a selector list as its argument, and selects any element that can be selected by one of the selectors in that list.&quot; - <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is">MDN docs on <code>:is()</code></a></p> </blockquote> <p>One way this can make a big impact is more compactly selecting typography elements such as:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>h1<span class="token punctuation">,</span> h2<span class="token punctuation">,</span> h3<span class="token punctuation">,</span> h4<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <p>Or to scope layout styles more succinctly, such as:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>header<span class="token punctuation">,</span> main<span class="token punctuation">,</span> footer<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <p>We can even combine <code>:is()</code> with <code>:not()</code> and really trim down our selectors, in this case selecting elements that are <em>not</em> headlines:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>h1<span class="token punctuation">,</span>h2<span class="token punctuation">,</span>h3<span class="token punctuation">,</span>h4<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre> <blockquote> <p>To see this selector in context, check out the <a href="https://smolcss.dev/#smol-card-component">Smol Composable Card Component</a> from the ModernCSS companion project, SmolCSS.dev.</p> </blockquote> <p>For the immediate future, you'll want to include at least the <code>webkit</code> prefix version if you want to start using this selector. Due to a quirk in how browsers use selectors, you'll want to make this a unique rule separate from <code>is()</code> to avoid the browser throwing <em>both</em> rules out.</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">-webkit-any</span><span class="token punctuation">(</span>header<span class="token punctuation">,</span> main<span class="token punctuation">,</span> footer<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Style the Current Anchor Linked Element</h4> <p>When an element is the target of an anchor link (document fragment identifier) - <code>https://url.com/#anchor-here</code> - you can style it using <code>:target</code>.</p> <p>I rely on anchor links for my project 11ty.Rocks, such as can be seen visiting this link to the <a href="https://11ty.rocks/#11ty-css-houdini-worklet-generator">CSS Houdini Worklet Generator</a>.</p> <p>The <code>:target</code> pseudo class should be placed on the element that contains the <code>id</code> attribute. However, you can chain it with descendent selectors to affect nested elements - maybe you want to give <code>article:target h2</code> a larger size or something like that.</p> <p>Leveraging <code>:target</code> I add a little extra message by combining with the pseudo element <code>::before</code> to help indicate to the visitor which item they were provided a link for, which appears as follows (&quot;It's me you're looking for...&quot;)</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/76zuti0pqhsih2zr8peg.png" alt="the :target style as applied to an article on 11ty.Rocks which has the message as described prior to this image" /></p> <p><strong>Bonus tip</strong>: ensure a bit of spacing prior to the top of the element on scroll by using <code>scroll-margin-top: 2em;</code> (or another value of your choosing). This should be considered a progressive enhancement, be sure to <a href="https://caniuse.com/mdn-css_properties_scroll-margin-top">review browser support for <code>scroll-margin-top</code></a>.</p> <h4>Visually Indicate Visited Archive Links</h4> <p>The <code>:visited</code> pseudo class is very unique because of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector">the potential to be exploited in terms of user's privacy</a>. To resolve this, browser makers have limited which CSS styles are allowed to be applied using <code>:visited</code>.</p> <p>Una Kravets has a much more <a href="https://una.im/hacking-visited/">in-depth reference exploring how to create useful <code>:visited</code> styles</a>, but here's the reduced version which I have in use <a href="https://stylestage.dev/styles/">for visitors of Style Stage to track which styles they have already viewed</a>.</p> <p>A key gotcha is that styles applied via <code>:visited</code> will always use the parent's alpha channel - meaning, you cannot use <code>rgba</code> to go from invisible to visible, you must change the whole color value.</p> <p>So, to hide the initial state, you need to be able to use a solid color, such as the page background color.</p> <p>Additionally, for accessibility, it may not be desirable for the pseudo element content to be read out if it's an icon or emoji since we cannot supply an accessible name for <code>content</code> values. There is inconsistencies between assistive technology in whether pseudo element content is read, so we can try to ensure it's ignored with the use of <code>aria-hidden=&quot;true&quot;</code>.</p> <p>Our first step then is to add a span within links and that is what we will ultimately apply the <code>:visited</code> stylings to:</p> <pre class="language-html"><code class="language-html"><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 punctuation">></span></span>Article Title <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>The default styling (non-visited) adds the pseudo element, and makes the color the same as the page background to hide it visually:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a span[aria-hidden]::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">"✔"</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-background<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, when the link has been visited, we update the color to make it visible:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a:visited span[aria-hidden]::after</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Advanced Interactions With <code>:focus-within</code></h4> <p>An up and coming pseudo class is <code>:focus-within</code> for which a polyfill is available, but otherwise it should be used with caution or as a progressive enhancement.</p> <p>The <code>:focus-within</code> pseudo class as described by <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">MDN docs:</a></p> <blockquote> <p>The <code>:focus-within</code> CSS pseudo-class represents an element that has received focus or contains an element that has received focus. In other words, it represents an element that is itself matched by the <code>:focus</code> pseudo-class or has a descendant that is matched by <code>:focus</code>.</p> </blockquote> <p>For a practical way to use <code>:focus-within</code> review the tutorial for a <a href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/">CSS-Only Accessible Dropdown Navigation Menu</a>.</p> <div class="heading-wrapper h2"> <h2 id="pseudo-elements">Pseudo Elements</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements" aria-labelledby="pseudo-elements"><span hidden="">#</span></a></div> <p>Pseudo elements allow you to style a specific part of the selected element. They vary quite widely in application, with the (currently) best supported ones being the following:</p> <ul> <li><code>::after</code></li> <li><code>::before</code></li> <li><code>::first-letter</code></li> <li><code>::first-line</code></li> <li><code>::selection</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-pseudo-elements">Practical Applications For Pseudo Elements</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#practical-applications-for-pseudo-elements" aria-labelledby="practical-applications-for-pseudo-elements"><span hidden="">#</span></a></div> <h4>Extra Visual Elements For Styling Benefits</h4> <p>The <code>::before</code> and <code>::after</code> pseudo elements create an additional element that visually appears to be part of the DOM, but is not part of the real HTML DOM. They are as fully style-able as any real DOM element.</p> <p>I have used these elements for all sorts of embellishments. Since they act like real elements, they are computed as child elements when using flexbox or CSS grid layout, which has greatly increased their functionality in my toolbox.</p> <p>A few key concepts for using <code>::before</code> and <code>::after</code>:</p> <ul> <li>Requires the <code>content</code> property before being made visible, but this property can be set to a blank string - <code>content: &quot;&quot;;</code></li> <li>Critical text content should not be included in the <code>content</code> value since it's inconsistently accessed by assistive technology</li> <li>Unless positioned otherwise, <code>::before</code> will display prior to the main element content, and <code>::after</code> will display after it.</li> </ul> <p>Here's a demo of the default behavior of these with just a little bit of styling applied:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/h66jvp0e6svmbbw09sh1.png" alt="A short paragraph showing the the text of &quot;Before&quot; in sitting prior to the paragraph content and the text of &quot;After&quot; sitting after the paragraph content" /></p> <p>Notice they act like inline elements by default, and follow the wrapping behavior as well for longer content:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o01fjpavgllxqlcbubrk.png" alt="A slightly longer paragraph that has wrapping lines showing the the text of &quot;Before&quot; in sitting prior to the paragraph content and the text of &quot;After&quot; sitting after the paragraph content" /></p> <p>And here's with the singular adjustment to add <code>display: flex</code> to the paragraph:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/15693mr4uufc3hhxdbor.png" alt="The same multiline paragraph but the &quot;Before&quot; appears as a column to the left of the paragraph content, and &quot;After&quot; appears as a column after the paragraph content" /></p> <p>And here's with swapping that to <code>display: grid</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/n3trrnffml62n7oudlsl.png" alt="The same multiline paragraph but the &quot;Before&quot; appears as a row on top of the paragraph content, and &quot;After&quot; appears as a row below the paragraph content" /></p> <p>The <code>::before</code> and <code>::after</code> elements are quick ways to add simple, consistent typography flourishes, a few of which can be seen in this CodePen demo:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="PoGEwzB" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>Did you catch the trick in the emoji one?</p> <p>We can retrieve the value of any attribute on the element to use in the <code>content</code> property via the <code>attr()</code> function:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* &lt;h2 class="emoji" data-emoji="😍"> */</span> <span class="token selector">.emoji::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-emoji<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a gist of <a href="https://gist.github.com/5t3ph/d44a1677d68cf86eb0683d5050a84692">how to display element <code>id</code> and <code>class</code> values in pseudo elements</a> using this same idea.</p> <h4>Emphasize an Article Lead</h4> <p>The &quot;lede&quot; (pronounced &quot;lead&quot;) is a newsprint term for the first paragraph within a news article and is intended to be a summary of the key point of the article (you may have heard the phrase &quot;Don't bury the lede!&quot;).</p> <p>We can combine the pseudo class of <code>:first-of-type</code> with the pseudo element of <code>:first-line</code> to emphasize the first line of paragraph copy. Interestingly, this is dynamic and will change as the viewport size changes.</p> <pre class="language-css"><code class="language-css"><span class="token selector">article p:first-of-type:first-line</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.1em<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> darkcyan<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Producing the following inherently responsive result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/50ym2bam1ic7frl913fh.gif" alt="gif demo of resizing the viewport when the previously described rule is applied and seeing that as the number of words in the first line of the paragraph changes the style continues to only affect the very first line" /></p> <h4>Ensure Accessible Contrast For Text Selection</h4> <p>A frequently missed style is for text selection, despite it being an interaction many of us engage in multiple times a day.</p> <p>While browsers try to handle styling this event, it is possible to lose contrast. I encountered this when styling <a href="http://moderncss.dev/">ModernCSS.dev</a> due to the darker theme in use.</p> <p>To resolve, we can use the <code>::selection</code> pseudo element to supply a custom text color and background color:</p> <pre class="language-css"><code class="language-css"><span class="token selector">::selection</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> yellow<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Custom List Bullet Styles</h4> <p>An up and coming pseudo element specifically for styling list bullets is <code>::marker</code>.</p> <p>For the browser support link and an example of how to use it, check out the section in my tutorial on <a href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker">Totally Custom List Styles</a>.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional Resources</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors">MDN Docs</a> on CSS selectors</li> <li><a href="https://hugogiraudel.github.io/selectors-explained/">Selectors Explained</a> - enter any selector to learn what is being affected</li> <li><a href="https://polypane.app/css-specificity-calculator/">CSS specificity calculator by Polypane</a> - discover the level of specificity of a selector</li> <li><a href="https://flukeout.github.io/">CSS Diner</a> - a game to test your ability to create CSS selectors</li> <li><a href="https://stylestage.dev/">Style Stage</a> - a great place to practice your new selector knowledge is my other project, <a href="https://stylestage.dev/">Style Stage</a>, which is a modern CSS showcase styled by community contributions. A limitation is the inability to add new classes or IDs, so you will need to exercise your selector abilities to successfully create your stylesheet submission!</li> </ul> <blockquote> <p><strong><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/">Head back to part one to learn about five other categories of advanced CSS selectors</a></strong>. And, if you learned something from this guide and you're able, <a href="https://www.buymeacoffee.com/moderncss">I'd appreciate a coffee</a> to help me bring you more tutorials and resources!</p> </blockquote> Guide to Advanced CSS Selectors - Part One 2020-12-28T00:00:00Z https://moderncss.dev/guide-to-advanced-css-selectors-part-one/ <p>Whether you choose to completely write your own CSS, or use a framework, or be required to build within a design system - understanding selectors, the cascade, and specificity are critical to developing CSS and modifying existing style rules.</p> <p>You're probably quite familiar with creating CSS selectors based on IDs, classes, and element types. And you've likely often used the humble space character to select descendants.</p> <p>In this two-part mini-series, we'll explore some of the more advanced CSS selectors, and examples of when to use them.</p> <div class="heading-wrapper h2"> <h2 id="part-one-this-article">Part One (this article):</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#part-one-this-article" aria-labelledby="part-one-this-article"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade">CSS Specificity and the Cascade</a></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector">Universal selector</a> - <code>*</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector">Attribute selector</a> - <code>[attribute]</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator">Child combinator</a> - <code>&gt;</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator">General sibling combinator</a> - <code>~</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">Adjacent sibling combinator</a> - <code>+</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="part-two">Part Two:</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#part-two" aria-labelledby="part-two"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes">Pseudo classes</a> - ex: <code>:checked / :focus</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements">Pseudo elements</a> - ex: <code>::before / ::after</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources">Additional resources</a></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="css-specificity-and-the-cascade">CSS Specificity and the Cascade</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade" aria-labelledby="css-specificity-and-the-cascade"><span hidden="">#</span></a></div> <p>A key concept to successfully setting up CSS selectors is understanding what is known as CSS specificity, and the &quot;C&quot; in CSS, which is the cascade.</p> <blockquote> <p>Specificity is a weight that is applied to a given CSS declaration, determined by the number of each selector type in the matching selector. When multiple declarations have equal specificity, the last declaration found in the CSS is applied to the element. Specificity only applies when the same element is targeted by multiple declarations. As per CSS rules, directly targeted elements will always take precedence over rules which an element inherits from its ancestor. - <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">MDN docs</a></p> </blockquote> <p>Proper use of the cascade and selector specificity means you should be able to entirely avoid the use of <code>!important</code> in your stylesheets.</p> <p>Increasing specificity comes with the result of overriding inheritance from the cascade.</p> <p>As a small example - what color will the <code>.item</code> be?</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</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>specific<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>item<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <pre class="language-css"><code class="language-css"><span class="token selector">#specific .item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">span.item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>.item</code> will be <em>red</em> because the specificity of including the <code>#id</code> in the selector wins against the cascade and over the element selector.</p> <p>This doesn't mean to go adding <code>#ids</code> to all your elements and selectors, but rather to be aware of their impact on specificity.</p> <blockquote> <p><strong>Key concept</strong>: The higher the specificity, the more difficult to override the rule.</p> </blockquote> <p>Every project will be unique in its needs in terms of reusability of rules. The desire to share rules with low specificity has lead to the rise of CSS utility-driven frameworks such as Tailwind and Bulma.</p> <p>On the other hand, the desire to tightly control inheritance and specificity such as within a design system makes naming conventions like BEM popular. In those systems, a parent selector is tightly coupled with child selectors to create reusable components that create their own specificity bubble.</p> <p>If you're thinking &quot;I don't need to learn these because I use a framework/design system&quot; then you are greatly limiting yourself in terms of using CSS to its fullest extent.</p> <p>The beauty of the language can be found in constructing elegant selectors that do <em>just enough</em> and enable tidy small stylesheets.</p> <div class="heading-wrapper h2"> <h2 id="universal-selector">Universal Selector</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector" aria-labelledby="universal-selector"><span hidden="">#</span></a></div> <p>The universal selector - <code>*</code> - is so named because it applies to all elements universally.</p> <p>There used to be recommendations against using it, particularly as a descendent, because of performance concerns, but that is no longer a valid consideration. In fact, it hasn't been a concern in over a decade. Instead, worry about your JS bundle size and ensuring your images are optimized rather than finessing CSS selectors for performance reasons 😉</p> <p>A better reason to only use it sparingly is that it has zero specificity when used by itself, meaning it can be overridden by a single id, class, or element selector.</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-universal-selector">Practical Applications For the Universal Selector</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-universal-selector" aria-labelledby="practical-applications-for-the-universal-selector"><span hidden="">#</span></a></div> <h4>CSS Box Model Reset</h4> <p>My most common usage is as my very first CSS reset rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*, *::before, *::after</span> <span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This means that we want all elements to <em>include</em> padding and borders in the box model calculation instead of <em>adding</em> those widths to any defined dimensions. For example, in the following rule, the <code>.box</code> will be <code>200px</code> wide, not <code>200px + 20px</code> from the padding:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 200px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Vertical Rhythm</h4> <p>Another very useful application was recommended by Andy Bell and Heydon Pickering in their <a href="https://every-layout.dev/">Every Layout</a> site and book and is called &quot;<a href="https://every-layout.dev/layouts/stack/">The Stack</a>&quot; which in it's most simple form looks like this:</p> <pre class="language-css"><code class="language-css"><span class="token selector">* + *</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>When used with a reset or parent rule that reduces all element margins to zero, this applies a top margin to all elements <em>that follow another element</em>. This is a quick way to gain vertical rhythm.</p> <p>If you do want to be a little more - well, selective - then I enjoy using it as a descendent in specific circumstances such as the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">article * + h2</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 4rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is similar to the stack idea, but more targeted towards the headline elements to provide a bit more breathing room between content sections.</p> <div class="heading-wrapper h2"> <h2 id="attribute-selector">Attribute Selector</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector" aria-labelledby="attribute-selector"><span hidden="">#</span></a></div> <p>This is an exceedingly powerful category and yet often not used to its full potential.</p> <p>Did you know you can achieve matching results similar to regex by leveraging CSS attribute selectors?</p> <p>This is exceptionally useful for modifying BEM styled systems or other frameworks that use related class names but perhaps not a single common class name.</p> <p>Let's see an example:</p> <pre class="language-css"><code class="language-css">[class*=<span class="token string">"component_"</span>]</code></pre> <p>This selector will select all elements which have a class that <em>contains</em> the string of &quot;component_&quot;, meaning it will match &quot;component_title&quot; and &quot;component_content&quot;.</p> <p>And you can ensure the match is case insensitive by including <code>i</code> prior to closing the attribute selector:</p> <pre class="language-css"><code class="language-css">[class*=<span class="token string">"component_"</span> i]</code></pre> <p>But you don't have to specify an attribute value, you can simply check if it's present, such as:</p> <pre class="language-css"><code class="language-css">a[class]</code></pre> <p>Which would select <em>all</em> <code>a</code> (link elements) that have <em>any</em> class value.</p> <blockquote> <p>Review the MDN docs for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">all the possible ways to match values within attribute selectors</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-attribute-selectors">Practical Applications For Attribute Selectors</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-attribute-selectors" aria-labelledby="practical-applications-for-attribute-selectors"><span hidden="">#</span></a></div> <h4>Assist in Accessibility Linting</h4> <p>These selectors can be wielded to perform some basic accessibility linting, such as the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">img:not([alt])</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This will add an outline to all images that do not include an <code>alt</code> attribute.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Attach to Aria to Enforce Accessibility</h4> <p>Attribute selectors can also help enforce accessibility if they are used as the <em>only</em> selector, making the absence of the attribute prevent the associated styling. One way to do this is by attaching to required <code>aria</code> attributes</p> <p>One example is when implementing an accordion interaction where you need to include the following button, whether the aria boolean is toggled via JavaScript:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</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 punctuation">></span></span>Toggle<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <p>The related CSS could then use the <code>aria-expanded</code> as an attribute selector alongside the adjacent sibling combinator to style the related content open or closed:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button[aria-expanded="false"] + .content</span> <span class="token punctuation">{</span> <span class="token comment">/* hidden styles */</span> <span class="token punctuation">}</span> <span class="token selector">button[aria-expanded="true"] + .content</span> <span class="token punctuation">{</span> <span class="token comment">/* visible styles */</span> <span class="token punctuation">}</span></code></pre> <h4>Styling Non-Button Navigation Links</h4> <p>When dealing with navigation, you may have a mix of default links and links stylized as &quot;buttons&quot;. In this case, it can be very useful to use the following to select non-&quot;button&quot; links:</p> <pre class="language-css"><code class="language-css">nav <span class="token property">a</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[class]<span class="token punctuation">)</span></code></pre> <h4>Remove Default List Styling</h4> <p>Another tip I've started incorporating from Andy Bell and his <a href="https://piccalil.li/blog/a-modern-css-reset/">modern CSS reset</a> is to remove list styling based on the presence of the <code>role</code> attribute:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */</span> <span class="token selector">ul[role="list"], ol[role="list"]</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="child-combinator">Child Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator" aria-labelledby="child-combinator"><span hidden="">#</span></a></div> <p>The child combinator selector - <code>&gt;</code> - is very effective at adding just a bit of specificity to reduce scope when applying styles to element descendants. It is the only selector that deals with <em>levels</em> of elements and can be compounded to select nested elements.</p> <p>The child combinator scopes descendent styling from <em>any</em> descendent that matches the child selector to only <em>direct descendants</em>.</p> <p>In other words, whereas <code>article p</code> selects <em>all</em> <code>p</code> within <code>article</code>, <code>article &gt; p</code> selects only paragraphs that are directly within the article, not nested within other elements.</p> <p>✅ Selected with <code>article &gt; p</code></p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>🚫 Not selected with <code>article &gt; p</code></p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>blockquote</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>blockquote</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-child-combinator">Practical Applications For the Child Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-child-combinator" aria-labelledby="practical-applications-for-the-child-combinator"><span hidden="">#</span></a></div> <h4>Nested Navigation List Links</h4> <p>Consider a sidebar navigation list, such as for a documentation site, where there are nested levels of links. Semantically, this means an outer <code>ul</code> and also nested <code>ul</code> within <code>li</code>.</p> <p>For visual hierarchy, you likely want to style the top-level links differently than the nested links. To target only the top-level links, you can use the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">nav > ul > li > a</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a CodePen where you can <a href="https://codepen.io/5t3ph/pen/dypZwJd">experiment with what happens if you remove any of the child combinators</a> in that selector.</p> <h4>Scoping Element Selectors</h4> <p>I enjoy using element selectors for the foundational things in my page layouts, such as <code>header</code> or <code>footer</code>. But you can get into trouble since those elements are valid children of certain other elements, such as <code>footer</code> within a <code>blockquote</code> or an <code>article</code>.</p> <p>In this case, you may want to adjust from <code>footer</code> to <code>body &gt; footer</code>.</p> <h4>Styling Embedded / Third-Party Content</h4> <p>Sometimes you truly do not have control over class names, IDs, or even markup. For example, for ads or other JavaScript-driven (non-iframe) content.</p> <p>In this case, you may be faced with a sea of divs or spans, in which case the child combinator can be very useful for attaching styles to varying levels of content.</p> <blockquote> <p>Note that many of the other selectors discussed can help in this scenario as well, but only the child combinator deals with <em>levels</em> and can affect nested elements.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="general-sibling-combinator">General Sibling Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator" aria-labelledby="general-sibling-combinator"><span hidden="">#</span></a></div> <p>The general sibling combinator - <code>~</code> - selects the defined elements that are located <em>somewhere after</em> the preceding (prior defined) element and that are <em>within the same parent</em>.</p> <p>For example, <code>p ~ img</code> would style all images that are located <em>somewhere after</em> a paragraph provided they share the same parent.</p> <p>This means all the following images would be selected:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Paragraph<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Headline 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Headline 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>But <strong>not</strong> the image in this scenario:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Paragraph<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>It is likely you would want to be a bit more specific (see also: the adjacent sibling combinator), and this selector tends to be used most in creative coding exercises, such as my <a href="https://codepen.io/5t3ph/pen/ExPVEZP">CommitSweeper pure CSS game</a>.</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-general-sibling-combinator">Practical Applications For the General Sibling Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-general-sibling-combinator" aria-labelledby="practical-applications-for-the-general-sibling-combinator"><span hidden="">#</span></a></div> <h4>Visual Indication of A State Change</h4> <p>Combining the general sibling combinator with stateful pseudo class selectors, such as <code>:checked</code>, can produce interesting results.</p> <p>Given the following HTML for a checkbox:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>terms<span class="token punctuation">"</span></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>checkbox<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>terms<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>I accept the terms<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- series of &lt;p> with the terms content --></span></code></pre> <p>We can alter the style of the terms paragraphs using the general sibling combinator <em>only when</em> the checkbox has been checked:</p> <pre class="language-css"><code class="language-css"><span class="token selector">#terms:checked ~ p</span> <span class="token punctuation">{</span> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #797979<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Low Specificity Variations</h4> <p>If we also pull in the universal selector, we can quickly generate slight variations such as for simple card layouts.</p> <p>Rather than moving content around and in and out of nested divs with classes to alter the arrangement of the headline and paragraphs, we can use the general sibling combinator instead to produce the following variations.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/g4e3fw68cot36qrb9k1p.png" alt="Three card layouts including a headline, two paragraphs, and an image. Any content that follows an image gains the style described below." /></p> <p>The rule adds some margin, reduces the font size and lightens the text color for any element that follows the image:</p> <pre class="language-css"><code class="language-css"><span class="token selector">img ~ *</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.9rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #797979<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem 1rem 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can <a href="https://codepen.io/5t3ph/pen/VwKrqVB">experiment with these general sibling combinator results in this CodePen</a>.</p> <p>This rule has extremely low specificity, so you could easily override it by adding a more targeted rule.</p> <p>Additionally, since it only applies when the elements are shared direct descendants of the image's parent - a <code>li</code> in this case - once you wrap the content in another element the rule will only apply up until inheritance is in use by child elements. To better understand this, try wrapping the content in the last card item in a div. The color and margin will be inherited on the <code>div</code> and type elements, but the native browser styling on the <code>h3</code> prevents the <code>font-size</code> from the general sibling combinator from being inherited since the native browser rule has higher specificity than the universal selector which is technically targeting the <code>div</code>.</p> <div class="heading-wrapper h2"> <h2 id="adjacent-sibling-combinator">Adjacent Sibling Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator" aria-labelledby="adjacent-sibling-combinator"><span hidden="">#</span></a></div> <p>The adjacent sibling combinator - <code>+</code> - selects the element that directly follows the preceding (prior defined) element.</p> <p>We've already used this within the universal selector examples - <code>* + *</code> - to apply a top margin to only elements that follow another element - so let's get right to more examples!</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-adjacent-sibling-combinator">Practical Applications For the Adjacent Sibling Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-adjacent-sibling-combinator" aria-labelledby="practical-applications-for-the-adjacent-sibling-combinator"><span hidden="">#</span></a></div> <h4>Polyfill For Lack fo Flexbox Gap Support in Navigation</h4> <p><a href="https://caniuse.com/flexbox-gap">Flexbox gap support</a> is on the way, but at the time of writing is notably not yet available in Safari outside of their technology preview.</p> <p>So, in the case of something like website navigation where flex layout is very useful, we can use the adjacent sibling combinator to assist in adding margin as a polyfill for <code>gap</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">nav ul li + li</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which enables a gap effect between list items without needing to clear out an extra margin on the first:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/q92z4bj0yhirnp5w9exh.png" alt="Result of the previously described rule where the flex inlined link items have space between them provided by margin-left" /></p> <h4>Applying Spacing Between Form Labels and Inputs</h4> <p>The theory being applied in &quot;the stack&quot; that we explored for universal selectors is to only apply margin in one direction.</p> <p>We can use this idea scoped to form field objects to provide enough spacing to retain visual hierarchy between field types. In this case, we add a much smaller margin between a label and it's input, and a larger margin between inputs and labels:</p> <pre class="language-css"><code class="language-css"><span class="token selector">label + input</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input + label</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>Note</em>: This example works in a limited context. You probably want to enclose field types with a grouping element to ensure consistency between field types, vs. list all field types besides <code>input</code> such as <code>select</code> and <code>textarea</code>. If forms design is of interest to you, check out the mini-series here on ModernCSS and stay tuned for my upcoming <a href="https://5t3ph.dev/egghead">egghead course</a> about cross-browser form field styling.</p> <blockquote> <p><strong><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/">Continue to part two where we'll learn about pseudo classes and pseudo elements</a></strong>. A great place to practice your new selector knowledge is my other project, <a href="https://stylestage.dev/">Style Stage</a> - a modern CSS showcase styled by community contributions. And, if you learned something from this guide and you're able, <a href="https://www.buymeacoffee.com/moderncss">I'd appreciate a coffee</a> to help me bring you more tutorials and resources!</p> </blockquote> The 3 CSS Methods for Adding Element Borders 2020-12-11T00:00:00Z https://moderncss.dev/the-3-css-methods-for-adding-element-borders/ <p>When it comes to CSS, sometimes a <code>border</code> is not really a <code>border</code>.</p> <p>In this episode, we'll cover the differences between:</p> <ul> <li><code>border</code></li> <li><code>outline</code></li> <li><code>box-shadow</code></li> </ul> <p>We'll also discuss when you might use one over the other.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="refresher-on-the-css-box-model">Refresher on the CSS Box Model</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#refresher-on-the-css-box-model" aria-labelledby="refresher-on-the-css-box-model"><span hidden="">#</span></a></div> <p>A key difference between our three border methods is <em>where</em> they are placed on an element and <em>how</em> they affect its dimensions. This behavior is controlled by the CSS box model.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4f0uy1wh7h3brj8361h4.png" alt="An illustration of the CSS box model, with the relevant parts described following this image" /></p> <ul> <li>the <code>border</code> is precisely the boundary of the element, sitting between its padding and margin, and it's width will impact the computed element dimensions</li> <li>the <code>outline</code> is next to but outside of the <code>border</code>, overlapping both <code>box-shadow</code> and <code>margin</code>, but not affecting the element's dimensions</li> <li>by default, <code>box-shadow</code> extends out from edge of the border covering the amount of space in the direction(s) defined, and it will also not affect the element's dimensions</li> </ul> <div class="heading-wrapper h2"> <h2 id="border-syntax-and-usage"><code>border</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#border-syntax-and-usage" aria-labelledby="border-syntax-and-usage"><span hidden="">#</span></a></div> <p>Borders have been a standard in design since the beginning of the web.</p> <p>An important difference compared to the other two methods we're going to cover is that <em>by default</em> borders are included in the computed dimensions of an element. Even if you set <code>box-sizing: border-box</code> the borders still figure into the calculation.</p> <p>The most essential syntax for a border defines it's width and style:</p> <pre class="language-css"><code class="language-css"><span class="token property">border</span><span class="token punctuation">:</span> 3px solid<span class="token punctuation">;</span></code></pre> <p>If not defined, the default color will be <code>currentColor</code> which means it will use the nearest definition for <code>color</code> in the cascade.</p> <p>But there are more styles available for border, and using <code>border-style</code> you can define a different style for each side if you'd like.</p> <blockquote> <p>Visit <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-style">MDN docs</a> to review all available <code>border-style</code> values and see examples.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use-border">When to Use <code>border</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-border" aria-labelledby="when-to-use-border"><span hidden="">#</span></a></div> <p>Border is a solid choice (pun intended) for when it's acceptable for the style to be computed in the element's dimensions. And the default styles give a lot of design flexibility.</p> <blockquote> <p>Keep reading for a <strong>bonus tip</strong> about something only <code>border</code> can do!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="outline-syntax-and-usage"><code>outline</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#outline-syntax-and-usage" aria-labelledby="outline-syntax-and-usage"><span hidden="">#</span></a></div> <p>For outlines, the only required property to make it visible is to provide a style since the default is <code>none</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">outline</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span></code></pre> <p>Like border, it will gain color via <code>currentColor</code> and it's default width will be <code>medium</code>.</p> <p>The typical application of <code>outline</code> is by native browser styles on <code>:focus</code> of interactive elements like links and buttons.</p> <p>This particular application should <em>be allowed to occur</em> for purposes of accessibility unless you are able to provide a custom <code>:focus</code> style that meets the <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html">WCAG Success Criterion 2.4.7 Focus Visible</a>.</p> <p>For design purposes, an often noted issue with <code>outline</code> is that it is unable to inherit the curve from any <code>border-radius</code> styles.</p> <div class="heading-wrapper h3"> <h3 id="when-to-use-outline">When to Use <code>outline</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-outline" aria-labelledby="when-to-use-outline"><span hidden="">#</span></a></div> <p>Use of <code>outline</code> may be desirable when you don't want to impact the element's dimensions, and you don't need it to follow a <code>border-radius.</code> It happens to use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/outline-style">the same style values as border</a> so you can achieve a similar look.</p> <div class="heading-wrapper h2"> <h2 id="box-shadow-syntax-and-usage"><code>box-shadow</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#box-shadow-syntax-and-usage" aria-labelledby="box-shadow-syntax-and-usage"><span hidden="">#</span></a></div> <p>The minimal required properties for <code>box shadow</code> are values for the <code>x</code> and <code>y</code> axis and a color:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 5px 8px black<span class="token punctuation">;</span></code></pre> <p>Optionally, add a third unit to create <code>blur</code>, and a fourth to add <code>spread</code>.</p> <blockquote> <p>Check out <a href="https://5t3ph.dev/box-shadow">my 4.5 minute video demo on egghead</a> to learn more about the expanded syntax as well as tips on using <code>box-shadow</code>.</p> </blockquote> <p>To use it to create a border, we set the <code>x</code> and <code>y</code> axis values as well as the <code>blur</code> to <code>0</code>. Then set a positive number for <code>spread</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px blue<span class="token punctuation">;</span></code></pre> <p>This will create the appearance of a border around the element <em>and</em> it can even follow an applied <code>border-radius</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/fcym33w4yzwxjqwpqu0v.png" alt="an element using box-shadow in place of a border for the effect of a rounded blue border" /></p> <div class="heading-wrapper h3"> <h3 id="when-to-use-box-shadow">When to Use <code>box-shadow</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-box-shadow" aria-labelledby="when-to-use-box-shadow"><span hidden="">#</span></a></div> <p>You may prefer <code>box-shadow</code> particularly when you want to animate a border without causing layout shift. The next section will demonstrate an example of this context.</p> <p>And since <code>box-shadow</code> can be layered, it's an all-around good tool to get to know to enhance your layouts.</p> <p>However, it will not be able to fully mimic some of the styles provided by <code>border</code> and <code>outline</code>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="putting-it-all-together">Putting It All Together</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#putting-it-all-together" aria-labelledby="putting-it-all-together"><span hidden="">#</span></a></div> <p>Here are a few practical scenarios where you may need to use a <code>border</code> alternative.</p> <div class="heading-wrapper h3"> <h3 id="button-borders">Button Borders</h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#button-borders" aria-labelledby="button-borders"><span hidden="">#</span></a></div> <p>A common case of the real <code>border</code> becoming an issue is when providing styles for both bordered and non-bordered buttons, and the scenario of them lining up next to each other.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0bttmgk48k3vb3dygjr6.png" alt="a button using a border which appears visually larger than the second button with a background but no border" /></p> <p>A typical solution is usually increasing the non-bordered button dimensions equal to the <code>border-width</code>.</p> <p>An alternate solution with our new knowledge is to use <code>box-shadow</code> along with the <code>inset</code> keyword to place the pseudo border visually <em>inside</em> the button:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/r1phaj5fbfqute0b5gri.png" alt="updated styles using box-shadow on the first button resulting in the buttons appearing equal size" /></p> <p><em>Note that your padding will have to be larger than the <code>border-width</code> to prevent overlap of the text content</em>.</p> <p>Alternatively, perhaps you want to <em>add</em> a border on <code>:hover</code> or <code>:focus</code>. Using the real <code>border</code>, you will have an undesirable visual jump from layout shift since the <code>border</code> will briefly increase the dimensions in those states.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v5ayfxarp501kawc5tre.gif" alt="demo of a border being added on hover and causing the button to jump in place" /></p> <p>In this case, we can use <code>box-shadow</code> to create the pseudo border so that the true dimensions are not increased - plus we can animate it using <code>transition</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5x1yluygxigo1uan1p5r.gif" alt="demo of the box-shadow border on hover which no longer causes the button to jump" /></p> <p>Here's the reduced code for the above example:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 180ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:hover</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px tomato<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="upgrading-your-css-debugging-method">Upgrading Your CSS Debugging Method</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#upgrading-your-css-debugging-method" aria-labelledby="upgrading-your-css-debugging-method"><span hidden="">#</span></a></div> <p>A classic CSS joke is that to figure out CSS issues particularly for overflow scrolling or positioning is to add:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which will add a red border to every element.</p> <p>But as we've learned, this will also affect their computed dimensions, thus <em>potentially</em> accidentally causing you additional issues.</p> <p>Instead, use:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><em>Pop quiz</em>: where will the <code>outline</code> be placed, and why is this a better solution?</p> </blockquote> <p>One potential consequence of using <code>border</code> is <em>adding</em> scrollbars once content is re-drawn. This side-effect will not happen when using <code>outline</code>.</p> <p>Additionally, you're likely to be using <code>border</code> for elements already, so universally changing the <code>border</code> will cause layout shifts which is certainly likely to introduce new problems.</p> <blockquote> <p><em>Side note</em>: Browser DevTools also have evolved more sophisticated methods of helping you identify elements, with <a href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Debug_Scrollable_Overflow">Firefox</a> even adding both a &quot;scroll&quot; and &quot;overflow&quot; tag that is helpful in the case of debugging for overflow. I encourage you to spend some time learning more about DevTool features!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="ensuring-visible-focus">Ensuring Visible Focus</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#ensuring-visible-focus" aria-labelledby="ensuring-visible-focus"><span hidden="">#</span></a></div> <p>For accessibility, one scenario you may not be aware of is users of Windows High Contrast Mode (WHCM).</p> <p>In this mode, your provided colors are stripped away to a reduced high contrast palette. Not all CSS properties are allowed, including <code>box-shadow</code>.</p> <p>One practical impact is that if you have removed <code>outline</code> upon <code>:focus</code> and replaced it with <code>box-shadow</code>, users of WHCM will no longer be given visible focus.</p> <p>To remove this negative impact, you can apply a <code>transparent</code> outline on <code>:focus</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>For a bit more context on this specific issue, review <a href="https://moderncss.dev/css-button-styling-guide/#focus">the episode on button styling</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="pitfalls-of-outline-and-box-shadow">Pitfalls of <code>outline</code> and <code>box-shadow</code></h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#pitfalls-of-outline-and-box-shadow" aria-labelledby="pitfalls-of-outline-and-box-shadow"><span hidden="">#</span></a></div> <p>Because <code>outline</code> and <code>box-shadow</code> sit <em>outside</em> of the border in the box model, one consequence you may encounter is having them disappear under the edges of the viewport. So, you may need to add <code>margin</code> to the element or <code>padding</code> to the <code>body</code> as a countermeasure if you want it to remain visible.</p> <p>Their placement also means they can be sheared off by properties such as <code>overflow: hidden</code> or the use of <code>clip-path</code>.</p> <div class="heading-wrapper h2"> <h2 id="bonus-tip-gradient-borders">Bonus Tip: Gradient Borders</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#bonus-tip-gradient-borders" aria-labelledby="bonus-tip-gradient-borders"><span hidden="">#</span></a></div> <p>As promised, here's a bonus tip about something that - of the methods we've discussed - only <code>border</code> can do.</p> <p>An often forgotten border-related property is <code>border-image</code>. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-image">The syntax can be a bit strange</a> when trying to use it with actual images.</p> <p>But it has a hidden power - it also allows you to create gradient borders since CSS gradients are technically images:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jczxtsix50q9exeprh33.png" alt="preview of an element that has a pastel rainbow gradient applied with the text &quot;Hello World&quot;" /></p> <p>This requires defining a regular border width and style (although it will only display as <code>solid</code> regardless of style value), followed by the <code>border-image</code> property that can use the gradient syntax with one addition. The number after the gradient is the <code>slice</code> value which for our gradient we can simply use a value of <code>1</code> to essentially not alter the sizing/distortion of the gradient.</p> <pre class="language-css"><code class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid<span class="token punctuation">;</span> <span class="token comment">/* simplified from preview image */</span> <span class="token property">border-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to right<span class="token punctuation">,</span> purple<span class="token punctuation">,</span> pink<span class="token punctuation">)</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>To place the border on only one side, be sure to set the other sides to zero first or some browsers will still add it to all sides:</p> <pre class="language-css"><code class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">border-left-width</span><span class="token punctuation">:</span> 3px<span class="token punctuation">;</span> <span class="token comment">/* border-image */</span> <span class="token punctuation">}</span></code></pre> <p>The downside is that these borders do <em>not</em> respect <code>border-radius</code>, so if you need a solution that does, you can use Inspector to peek at how the gradients are added for the cards on the <a href="https://moderncss.dev/">ModernCSS</a> home page 😉</p> Pure CSS Shapes 3 Ways 2020-11-15T00:00:00Z https://moderncss.dev/pure-css-shapes-3-ways/ <p>Modern CSS - and modern browser support - provides us three excellent methods to create pure, basic CSS shapes. In this tutorial, we will examine how to create CSS triangles using:</p> <ul> <li>borders</li> <li>linear gradients</li> <li><code>clip-path</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="method-1-borders">Method 1: Borders</h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-1-borders" aria-labelledby="method-1-borders"><span hidden="">#</span></a></div> <p>This is the first trick I learned to create CSS triangles, and it's still a solid standby.</p> <p>Given a zero width and zero height element, any values provided <code>border</code> directly intersect and are the only visible indication of an element. This intersection is what we can take advantage of to create a triangle shape.</p> <p>To illustrate how this works, we'll supply a different border color to each side:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid blue<span class="token punctuation">;</span> <span class="token property">border-right-color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token property">border-bottom-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which produces the following, in which you can see we've essentially already achieved 4 triangle shapes:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/87uek6hvhhb8t9anzdq9.png" alt="result of the previously defined CSS rule showing 4 triangles due to the border colors" /></p> <p>In order to create this as a single triangle instead, we first decide which direction we want the triangle pointing. So, if we want it pointing to the right, similar to a &quot;play&quot; icon, we want to keep the left border visible. Then, we set the colors of the other borders to <code>transparent</code>, like so:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid transparent<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In the demo image below, I've added a red <code>outline</code> to see the bounding box so we can discuss some improvements.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/16idiqp96hp2uynw4zwd.png" alt="a blue triangle shape pointing to the right with a red outline to show the bounding box" /></p> <p>One improvement we can make is to remove width of the right border to prevent it being included in the total width of the element. We can also set unique values for top and bottom to elongate the triangle visual. Here's a compact way to achieve these results:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token comment">/* top | right | bottom | left */</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 7px 0 7px 10px<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As seen in the updated image below, we first assign a solid, transparent border. Then we define widths such that the top and bottom are a smaller value than the left to adjust the aspect ratio and render an elongated triangle.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0p5n46utvytk2np0uc8h.png" alt="final triangle" /></p> <p>So to point the triangle a different direction, such as up, we just shuffle the values so that the <em>bottom</em> border gains the color value and the <em>top</em> border is set to zero:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token comment">/* top | right | bottom | left */</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 0 7px 10px 7px<span class="token punctuation">;</span> <span class="token property">border-bottom-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Resulting in:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hxiym0rl9k9ygzshnscn.png" alt="demo of the CSS triangle pointing upwards" /></p> <p>Borders are very effective for triangles, but not very extendible beyond that shape without getting more elements involved. This is where our next two methods come to the rescue.</p> <div class="heading-wrapper h2"> <h2 id="method-2-linear-gradient">Method 2: <code>linear-gradient</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-2-linear-gradient" aria-labelledby="method-2-linear-gradient"><span hidden="">#</span></a></div> <p>CSS gradients are created as <code>background-image</code> values.</p> <p>First let's set our stage, if you will, by defining box dimensions and preventing <code>background-repeat</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 8em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 10em<span class="token punctuation">;</span> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span> <span class="token comment">/* Optional - helping us see the bounding box */</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we'll add our first gradient. This will create the appearance of coloring half of our element blue because we are creating a hard-stop at 50% between blue and a transparent value.</p> <pre class="language-css"><code class="language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>45deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Now, if our element was square, this would appear to cut corner to corner, but we ultimately want a slightly different aspect ratio like we did before.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/guscw9fd5wf7xfgkteje.png" alt="progress of adding the first gradient showing a partly blue element but not yet a triangle" /></p> <p>Our goal is to create a triangle with the same appearance as when using our border method. To do this, we will have to adjust the <code>background-size</code> and <code>background-position</code> values.</p> <p>The first adjustment is to change the <code>background-size</code>. In shorthand, the first value is width and the second height. We want our triangle to be allowed 100% of the width, but only 50% of the height, so add the following:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-size</span><span class="token punctuation">:</span> 100% 50%<span class="token punctuation">;</span></code></pre> <p>With our previous <code>linear-gradient</code> unchanged, this is the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/cb3cqq4h57jupkza659i.png" alt="updated triangle resized with background-size showing an odd shape in the upper left of the bounding box" /></p> <p>Due to the <code>45deg</code> angle of the gradient, the shape appears a bit strange. We need to adjust the angle so that the top side of the triangle appears to cut from the top-left corner to the middle of the right side of the bounding box.</p> <p>I'm not a math wizard, so this took a bit of experimentation using DevTools to find the right value 😉</p> <p>Update the <code>linear-gradient</code> value to the following:</p> <pre class="language-css"><code class="language-css"><span class="token function">linear-gradient</span><span class="token punctuation">(</span>32deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span>255<span class="token punctuation">,</span>255<span class="token punctuation">,</span>0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And here's our progress - which, while technically a triangle, isn't quite the full look we want:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/48gy0u5kf6pmp4uofkmw.png" alt="progress of completing one side of the triangle" /></p> <p>While for the border trick we had to rely on the intersection to create the shapes, for <code>linear-gradient</code> we have to take advantage of the ability to add multiple backgrounds to layer the effects and achieve our full triangle.</p> <p>So, we'll duplicate our <code>linear-gradient</code> and update it's degrees value to become a mirror-shape of the first, since it will be positioned below it. This results in the following for the full <code>background-image</code> definition:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>32deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>148deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>But - we still haven't quite completed the effect, as can be seen in the progress image:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/l57qenjmriispnhko0nm.png" alt="the second linear-gradient triangle is overlapping the first" /></p> <p>The reason for the overlap is because the default position of both gradients is <code>0 0</code> - otherwise known as <code>top left</code>. This is fine for our original gradient, but we need to adjust the second.</p> <p>To do this, we need to set multiple values on <code>background-position</code>. These go in the same order as <code>background-image</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-position</span><span class="token punctuation">:</span> top left<span class="token punctuation">,</span> bottom left<span class="token punctuation">;</span></code></pre> <p>And now we have our desired result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ylmk416mow1c48yrj1hb.png" alt="final triangle created with CSS gradients" /></p> <p>The downside of this method is that it's rather inflexible to changing aspect ratio without also re-calculating the degrees.</p> <p>However, CSS gradients can be used to create more shapes especially due to their ability to be layered to create effects.</p> <blockquote> <p>For a master class in CSS gradients for shapes and full illustrations, check out <a href="https://a.singlediv.com/">A Single Div</a> by Lynn Fisher</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="method-3-clip-path">Method 3: <code>clip-path</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-3-clip-path" aria-labelledby="method-3-clip-path"><span hidden="">#</span></a></div> <p>This final method is the slimmest and most scalable. It is currently <a href="https://caniuse.com/mdn-css_properties_clip-path_html">slightly lagging in support</a> so be sure to check our own analytics to determine if this is an acceptable solution.</p> <p>Here's our starting point for our element, which is box dimensions and a <code>background-color</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The concept of <code>clip-path</code> is that you use it to draw a polygon shape (or circle, or ellipse) and position it within the element. Any areas outside of the <code>clip-path</code> are effectively not drawn by the browser, thus &quot;clipping&quot; the appearance to just the bounds of the <code>clip-path</code>.</p> <blockquote> <p>To illustrate this more, and to generate your desired <code>clip-path</code> definition, check out the online generator: <a href="https://bennettfeely.com/clippy/">Clippy</a></p> </blockquote> <p>The syntax can be a bit more difficult to get used to, so I definitely suggest using the generator noted above to create your path.</p> <p>For our purposes, here's a triangle pointing to the right:</p> <pre class="language-css"><code class="language-css"><span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>0 0<span class="token punctuation">,</span> 0% 100%<span class="token punctuation">,</span> 100% 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>With a <code>clip-path</code>, you are defining coordinates for every point you place along the path. So in this case, we have a point at the top-left (<code>0 0</code>), bottom-left (<code>0% 100%</code>), and right-center (<code>100% 50%</code>).</p> <p>And here is our result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/we4fhuugu4dp2kvpt9ym.png" alt="completed triangle using clip-path" /></p> <p>While <code>clip-path</code> is very flexible for many shapes, and also the most scalable due to adapting to any bounding box or aspect ratio, there are some caveats.</p> <p>When I mentioned the browser doesn't draw anything outside of the bounding box, this includes borders, <code>box-shadow</code>, and <code>outline</code>. Those things are not re-drawn to fit the clipped shape. This can be a gotcha, and may require additional elements or moving of effects to a parent to replace the lost effects.</p> <blockquote> <p>Here's <a href="https://egghead.io/lessons/css-add-a-cutout-notch-to-an-html-element-with-a-css-polygon-clip-path?af=2s65ms">an egghead video by Colby Fayock</a> to better understand <code>clip-path</code> and how to bring back effects like <code>box-shadow</code></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>This demo shows our three ways to create a CSS triangle, which is added to each element using <code>::after</code> and makes use of viewport units to be responsive.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="oNLVvgX" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Custom CSS Styles for Form Inputs and Textareas 2020-08-31T00:00:00Z https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/ <p>We're going to create custom form input and textarea styles that have a near-identical appearance across the top browsers. We'll specifically style the input types of <code>text</code>, <code>date</code>, and <code>file</code>, and style the <code>readonly</code> and <code>disabled</code> states.</p> <p>Read on to learn how to:</p> <ul> <li>reset input styles</li> <li>use <code>hsl</code> for theming of input states</li> <li>ensure all states meet contrast requirements</li> <li>retain a perceivable <code>:focus</code> state for Windows High Contrast mode</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <blockquote> <p>This is the fourth installment in the Modern CSS form field mini-series. Check out episodes 18-20 to learn how to style other common form field types including <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a>, <a href="https://moderncss.dev/pure-css-custom-checkbox-style/">checkboxes</a>, and <a href="https://moderncss.dev/custom-select-styles-with-pure-css/">selects</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-input-styles">Common Issues with Native Input Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#common-issues-with-native-input-styles" aria-labelledby="common-issues-with-native-input-styles"><span hidden="">#</span></a></div> <p>There is a bit more parity between text input styles than we saw with radios, checkboxes, and selects, but inconsistencies nonetheless.</p> <p>Here's a screenshot of the unstyled inputs we're going to address today across (from left) Chrome, Safari, and Firefox.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7scyr2oalmyn8fce7z6x.png" alt="native input fields including text, date, file, and readonly and disabled states in the aforementioned browsers" /></p> <p>We will be looking to unify the initial appearance across browsers and common field types.</p> <blockquote> <p>The <code>date</code> field is unique in that Chrome and Firefox provide formatting and a popup calendar to select from, while Safari offers no comparable functionality. We cannot create this in CSS either, so our goal here is to get as far as we can with creating a similar initial appearance. Check out the <a href="https://caniuse.com/#feat=input-datetime">caniuse for date/time inputs</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="base-html">Base HTML</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#base-html" aria-labelledby="base-html"><span hidden="">#</span></a></div> <p>We're covering a lot of field types, so check the CodePen for the full list. But here is the essential HTML for a text input and a textarea.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Text Input<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>input<span class="token punctuation">"</span></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>text-input<span class="token punctuation">"</span></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>text<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>textarea<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Textarea<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>textarea</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>input<span class="token punctuation">"</span></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>textarea<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>textarea</span><span class="token punctuation">></span></span></code></pre> <p>To allow simplifying our styles and preparing to work with the cascade, we've only added one CSS class - <code>input</code> - which is placed directly on the text input and textarea.</p> <p>The label is not part of our styling exercise, but its included as a general requirement, notably with the <code>for</code> attribute having the value of the <code>id</code> on the input.</p> <div class="heading-wrapper h2"> <h2 id="create-css-variables-for-theming">Create CSS Variables for Theming</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#create-css-variables-for-theming" aria-labelledby="create-css-variables-for-theming"><span hidden="">#</span></a></div> <p>For the tutorial, we're going to try a bit different technique for theming by using <code>hsl</code> values.</p> <p>We'll set a grey for the border, and then break down a blue color to be used in our <code>:focus</code> state into its hsl values, including: <code>h</code> for &quot;hue&quot;, <code>s</code> for &quot;saturation&quot;, and <code>l</code> for &quot;lightness&quot;.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--input-border</span><span class="token punctuation">:</span> #8b8a8b<span class="token punctuation">;</span> <span class="token property">--input-focus-h</span><span class="token punctuation">:</span> 245<span class="token punctuation">;</span> <span class="token property">--input-focus-s</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--input-focus-l</span><span class="token punctuation">:</span> 42%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>Each of the tutorials for our form fields has incorporated a bit different method for theming, which can all be extracted and used beyond just forms!</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="accessible-contrast">Accessible Contrast</h3> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#accessible-contrast" aria-labelledby="accessible-contrast"><span hidden="">#</span></a></div> <p>As per all user interface elements, the input border needs to have <em>at least</em> 3:1 contrast against it's surroundings.</p> <p>And, the <code>:focus</code> state needs to have 3:1 contrast against the <em>unfocused</em> state if it involves something like changing the border color <em>or</em>, according to <a href="https://www.w3.org/TR/WCAG22/#focus-appearance-minimum">the WCAG 2.2 draft</a>, a thickness greater than or equal to <code>2px</code>.</p> <p><a href="https://www.w3.org/TR/WCAG22/#focus-appearance-minimum">The draft for WCAG 2.2</a> makes some slight adjustments to <code>:focus</code> requirements, and I encourage you to review them.</p> <div class="heading-wrapper h2"> <h2 id="reset-styles">Reset Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#reset-styles" aria-labelledby="reset-styles"><span hidden="">#</span></a></div> <p>As is included in all my tutorials as a modern best practice, we add the following reset first:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">*, *::before, *::after </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As seen in the initial state of the fields across browsers, some standout differences were in border type, background color, and font properties.</p> <p>Interestingly, <code>font-size</code> and <code>font-family</code> do not inherit from the document like typography elements do, so we need to explicitly set them as part of our reset.</p> <p>Also of note, an input's <code>font-size</code> should compute to at least 16px to avoid zooming being triggered upon interaction in mobile Safari. We can typically assume <code>1rem</code> equals <code>16px</code>, but we'll explicitly set it as a fallback and then use the newer CSS function <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max"><code>max</code></a> to set <code>16px</code> as the minimum in case it's smaller than <code>1em</code> (h/t to <a href="https://dev.to/danburzo/css-micro-tip-make-mobile-safari-not-have-to-zoom-into-inputs-1fc1">Dan Burzo</a> for this idea).</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>16px<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--input-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We set our <code>border</code> to use the theme variable, and also created a slightly rounded corner.</p> <p>After this update, we're already looking pretty good:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ljnbom4rejrx08mxsa9s.png" alt="updated input styles in Chrome, Safari, and Firefox which all show the inputs with unified grey borders and white backgrounds" /></p> <p>It may be difficult to notice in that screenshot, but another difference is the height of each field. Here's a comparison of the text input to the file input to better see this difference:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/elc4gv33zikdjbbvdekc.png" alt="text input field across browsers compared to file input" /></p> <p>Let's address this with the following which we are applying to our <code>.input</code> class as long as it is not placed on a <code>textarea</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:not(textarea)</span> <span class="token punctuation">{</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 2.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We included <code>line-height: 1</code> since when it's not a <code>textarea</code> it's impossible for an input to be multiline. We also set our height in <code>rem</code> due to considerations of specifically the file input type. If you know you will not be using a file input type, you could use <code>em</code> here instead for flexibility in creating various sized inputs.</p> <p>But, critically, we've lost differentiation between editable and <code>disabled</code> input types. We also want to define <code>readonly</code> with more of a hint that it's also un-editable, but still interactive. And we have a bit more work to do to smooth over the file input type. And, we want to create our themed <code>:focus</code> state.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="file-input-css">File Input CSS</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#file-input-css" aria-labelledby="file-input-css"><span hidden="">#</span></a></div> <p>Let's take another look at just our file input across Chrome, Safari, and Firefox:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mawyssxau7ha2co1dfkb.png" alt="current state of the file input styling across browsers" /></p> <p>We cannot style the button created by the browser, or change the prompt text, but the reset we provided so far did do a bit of work to allow our custom font to be used.</p> <p>We'll make one more adjustment to downsize the font just a bit as when viewed with other field types the inherited button seems quite large, and <code>font-size</code> is our only remaining option to address it. From doing that, we need to adjust the top padding since we set our padding up to be based on <code>em</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="file"]</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token property">padding-top</span><span class="token punctuation">:</span> 0.35rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you were expecting a fancier solution, there are plenty of folx who have covered those. My goal here was to provide you a baseline that you can then build from.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="readonly-css-style"><code>readonly</code> CSS Style</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#readonly-css-style" aria-labelledby="readonly-css-style"><span hidden="">#</span></a></div> <p>While not in use often, the <code>readonly</code> attribute prevents additional user input, although the value can be selected, and it is still discoverable by assistive tech.</p> <p>Let's add some styles to enable more of a hint that this field is essentially a placeholder for a previously entered value.</p> <p>To do this, we'll target any <code>.input</code> that also has the <code>[readonly]</code> attriute. Attribute selectors are a very handy method with wide application, and definitely worth adding to (or updating your awareness of) in your CSS toolbox.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input[readonly]</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> dotted<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #777<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In addition to swapping for a <code>dotted</code> border, we've also assigned it the <code>not-allowed</code> cursor and enforced a medium-grey text color.</p> <p>As seen in the following gif, the user cannot interact with the field except to highlight/copy the value.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/iouhzeibpjqshlwt66mf.gif" alt="the user mouses over the readonly field and is show the not-allowed cursor, and then double-clicks to highlight the value" /></p> <div class="heading-wrapper h2"> <h2 id="disabled-input-and-textarea-style">Disabled Input and Textarea Style</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#disabled-input-and-textarea-style" aria-labelledby="disabled-input-and-textarea-style"><span hidden="">#</span></a></div> <p>Similar to <code>readonly</code>, we'll use an attribute selector to update the style for disabled fields. We are attaching it to the <code>.input</code> class so it applies on textareas as well as our other input types.</p> <p>We'll make use of our CSS variable to update the border color to a muted grey, and the field background to a very light grey. We'll also again apply the <code>not-allowed</code> cursor as just an extra hint that the field is not interactive.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input[disabled]</span> <span class="token punctuation">{</span> <span class="token property">--input-border</span><span class="token punctuation">:</span> #ccc<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here is the result for both a text input and a textarea:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/laaw2w94zl43c187ojae.png" alt="Alt Text" /></p> <blockquote> <p><strong>Accessibility Note</strong>: <code>disabled</code> fields are not necessarily discoverable by assistive tech since they are not focusable. They also are not required to meet even the typical 3:1 contrast threshold for user interface elements, but we've kept with user expectations by setting them to shades of grey.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="textarea-styles">Textarea Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#textarea-styles" aria-labelledby="textarea-styles"><span hidden="">#</span></a></div> <p>Our <code>textarea</code> is really close, but there's one property I want to mention since it's unique to the inherent behavior of textareas.</p> <p>That property is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/resize"><code>resize</code></a>, which allows you to specify which direction the <code>textarea</code> can be resized, or if it even can at all.</p> <p>While you definitely should allow the <code>textarea</code> to retain the resize function under general circumstances, you can limit it to just vertical resizing to prevent layout breakage from a user dragging it really wide, for example.</p> <p>We'll apply this property by scoping our <code>.input</code> class to when it's applied on a <code>textarea</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">textarea.input</span> <span class="token punctuation">{</span> <span class="token property">resize</span><span class="token punctuation">:</span> vertical<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Try it out in the final CodePen demo!</p> <div class="heading-wrapper h2"> <h2 id="focus-state-styles"><code>:focus</code> State Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#focus-state-styles" aria-labelledby="focus-state-styles"><span hidden="">#</span></a></div> <p>Ok, we've completed the initial styles for our inputs and the textarea, but we need to handle for a very important state: <code>:focus</code>.</p> <p>We're going to go for a combo effect that changes the border color to a value that meets 3:1 contrast against the unfocused state, but also adds a <code>box-shadow</code> for a bit of extra highlighting.</p> <p>And here's why we defined our theme color of the focus state in hsl: it means we can create a variant of the border color by updating just the lightness value.</p> <p>First, we define the border color by constructing the full hsl value from the individual CSS variable values:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token property">border-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-h<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-s<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-l<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we add in the <code>box-shadow</code> which will only use blur to create essentially a double-border effect. <code>calc()</code> is acceptable to use inside <code>hsla</code>, so we use it to lighten the original value by 40%, and also allow just a bit of alpha transparency:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px <span class="token function">hsla</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-h<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-s<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-l<span class="token punctuation">)</span> + 40%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Note that we've now added a new context for our contrast, which is the <code>:focus</code> border vs. the <code>:focus</code> <code>box-shadow</code>, so ensure the computed difference for your chosen colors is at least 3:1 if using this method.</p> <p>Optionally, jump back up to the <code>.input</code> rule and add a <code>transition</code> to animate the <code>box-shadow</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">transition</span><span class="token punctuation">:</span> 180ms box-shadow ease-in-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Finally, we don't want to forget Windows High Contrast mode which will not see the <code>box-shadow</code> or be able to detect the border color change. So, we include a transparent outline for those users:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>We also use this technique in the episode covering <a href="https://moderncss.dev/css-button-styling-guide/">button styles</a>.</p> </blockquote> <p>Here's a gif demo of focusing into the text input:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jqawfxgvv2lb2x6f0q3w.gif" alt="keyboard focusing into and out of the text input" /></p> <p>And here's the appearance for the <code>readonly</code> field, since it has a different <code>border-style</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/lg358oqeytrd8kdnokwo.png" alt="the readonly field when focused" /></p> <p>In the CodePen HTML, there is a comment with an example of using an inline style to define an updated visual such as for an error state. Again, keep in mind that we are <em>lightening</em> the provided <code>--input-focus-l</code> value by 40%, <em>and</em> the focused border color must be at least 3:1 contrast against the unfocused color, so consider that when you alter the CSS variable values.</p> <div class="heading-wrapper h2"> <h2 id="input-mode-and-autocomplete">Input Mode and Autocomplete</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#input-mode-and-autocomplete" aria-labelledby="input-mode-and-autocomplete"><span hidden="">#</span></a></div> <p>There are two aditional attributes that can help improve the user experience, particularly on mobile, in addition to using the correct input type (ex: email).</p> <p>The first is defining the <code>inputmode</code>, which provides an altered keyboard or keypad that better matches the expected data. <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode">Read up on available <code>inputmode</code> values on MDN &gt;</a></p> <p>Second is <code>autocomplete</code> which has far more options than <code>on</code> or <code>off</code>. For example, I always appreciate that on iPhone when Google sends me a confirmation code by text the keyboard &quot;just knows&quot; what that value is. Turns out, that's thanks to <code>autocomplete=&quot;one-time-code&quot;</code>!</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete">Check out the full list of <code>autocomplete</code> values</a> that allow you to hint at the value expected and really boost the user experience of your forms for users that make use of auto-filling values.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>First, here's a final look at our solution across (from left) Chrome, Safari, and Firefox. The file input still sticks out a bit when viewed side by side, but in the flow of a form on an individual browser it's definitely acceptable.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/c05aw0mbb7ndptdn794z.png" alt="final input and textarea styles across the aforementioned browsers" /></p> <p>Here is the solution with all the field types we covered represented.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="KKzqEzz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Custom Select Styles with Pure CSS 2020-08-15T00:00:00Z https://moderncss.dev/custom-select-styles-with-pure-css/ <p>Modern CSS gives us a range of properties to achieve custom select styles that have a near-identical initial appearance for single, multiple, and disabled <code>select</code> elements across the top browsers.</p> <p>A few properties and techniques our solution will use:</p> <ul> <li><code>clip-path</code> to create the custom dropdown arrow</li> <li>CSS grid layout to align the native select and arrow</li> <li>custom CSS variables for flexible styling</li> <li><code>em</code> units for relative sizing</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-selects">Common Issues with Native Selects</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#common-issues-with-native-selects" aria-labelledby="common-issues-with-native-selects"><span hidden="">#</span></a></div> <p>As with all form field types, <code>&lt;select&gt;</code> varies across browsers in its initial appearance.</p> <p>From left to right, here is the initial appearance for <code>&lt;select&gt;</code> in Firefox, Chrome, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/s8g71cd7l6rywzrx3js8.png" alt="initial native select appearance with no custom styling" /></p> <p>The differences include box size, font-size, line-height, and most standout is the difference in how the dropdown indicator is styled.</p> <p>Our goal is to create the same initial appearance across these browsers, inclusive of multiple selects, and disabled states.</p> <blockquote> <p>Note: The dropdown list is still not stylable, so once the <code>&lt;select&gt;</code> is opened, it will still pick up the individual browser's styles for the <code>option</code> list. This is ok - we can deal with that to retain the free accessibility of a native select!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="base-html">Base HTML</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#base-html" aria-labelledby="base-html"><span hidden="">#</span></a></div> <p>We'll focus on a single <code>&lt;select&gt;</code> to begin.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>standard-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Standard Select<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <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>select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</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>standard-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 4<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 5<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option length<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Option that has too long of a value to fit <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>The label is not part of our styling exercise, but its included as a general requirement, notably with the <code>for</code> attribute having the value of the <code>id</code> on the <code>&lt;select&gt;</code>.</p> <p>To accomplish our custom styles, we've wrapped the native select in an extra div with class of <code>select</code> for simplicity in this tutorial.</p> <div class="heading-wrapper h2"> <h2 id="reset-and-remove-inherited-styles">Reset and Remove Inherited Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#reset-and-remove-inherited-styles" aria-labelledby="reset-and-remove-inherited-styles"><span hidden="">#</span></a></div> <p>As is included in all my tutorials as a modern best practice, we add the following reset first:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">*, *::before, *::after </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we can begin the rule for the native <code>select</code> and apply the following to rest its appearance:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select </span><span class="token punctuation">{</span> <span class="token comment">// A reset of styles, including removing the default dropdown arrow</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">// Additional resets for further consistency</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 1em 0 0<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>While most of those are likely familiar, the oddball out is <code>appearance</code>. This is an infrequently used property and you'll note that it is not quite where we'd like it for <a href="https://caniuse.com/#search=appearance">support</a>, but what it's primarily providing for us in this instance is the removal of the native browser dropdown arrow.</p> <blockquote> <p>Note: The CodePen is set up to use <a href="https://autoprefixer.github.io/">autoprefixer</a> which will add required pre-fixed versions of the <code>appearance</code> property. You may need to specifically set this up for your project, or manually add them. My <a href="https://5t3ph.github.io/html-sass-jumpstart/">HTML / Sass Jumpstart</a> includes autoprefixer as part of the production build.</p> </blockquote> <p>The good news is, we can add one more rule to gain removal of the arrow for lower IE versions if you need it:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select::-ms-expand </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>This tip found in the excellent article from Filament Group that shows <a href="https://www.filamentgroup.com/lab/select-css.html">an alternate method to create select styles</a></em>.</p> <p>The last part is to remove the default <code>outline</code>. Don't worry - we'll add a replacement later on for the <code>:focus</code> state!</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></code></pre> <p>And here's a gif of our progress. You can see there is now zero visual indication that this is a <code>select</code> prior to clicking on it:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d8i63lg16q68v8eyt5ef.gif" alt="demo of interacting with the reset select" /></p> <div class="heading-wrapper h2"> <h2 id="custom-select-box-styles">Custom Select Box Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#custom-select-box-styles" aria-labelledby="custom-select-box-styles"><span hidden="">#</span></a></div> <p>First, let's set up some CSS variables. This will allow our select to be flexibly re-colored such as to represent an error state.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--select-border</span><span class="token punctuation">:</span> #777<span class="token punctuation">;</span> <span class="token property">--select-focus</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">--select-arrow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--select-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><strong>Accessibility note</strong>: As a user interface element, the select border must have a 3:1 contrast or greater against the surrounding surface color.</p> </blockquote> <p>Now it's time to create the custom select styles which we will apply to the our wrapping <code>div.select</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 15ch<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--select-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to top<span class="token punctuation">,</span> #f9f9f9<span class="token punctuation">,</span> #fff 33%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>First, we set up some width constraints. The <code>min-width</code> and <code>max-width</code> values are mostly for this demo, and you may choose to drop or alter it for your use case.</p> <p>Then we apply some box model properties, including <code>border</code>, <code>border-radius</code>, and <code>padding</code>. Note the use of the <code>em</code> unit which will keep these properties proportional to the set <code>font-size</code>.</p> <p>In the reset styles, we set several properties to <code>inherit</code>, so here we define those, including <code>font-size</code>, <code>cursor</code>, and <code>line-height</code>.</p> <p>Finally, we supply it background properties, including a gradient for the slightest bit of dimension. If you remove the background properties, the select will be transparent and pick up the page background. This may be desirable, however, be aware and test the effects on contrast.</p> <p>And here's our progress: <img src="https://dev-to-uploads.s3.amazonaws.com/i/prn99ajlym5ehflhqia9.png" alt="updated select now has a visually apparent box appearance" /></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="custom-select-dropdown-arrow">Custom Select Dropdown Arrow</h3> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#custom-select-dropdown-arrow" aria-labelledby="custom-select-dropdown-arrow"><span hidden="">#</span></a></div> <p>For our dropdown arrow, we are going to use one of the most exciting modern CSS properties: <code>clip-path</code>.</p> <p>Clip paths let us make all kind of shapes by &quot;clipping&quot; the otherwise square and rectangle shapes we receive as defaults from most elements. I had fun using <code>clip-path</code> on <a href="https://thinkdobecreate.com/">my recent portfolio site redesign</a>.</p> <p>Prior to <code>clip-path</code> having better support, alternative methods included:</p> <ul> <li><code>background-image</code> - typically a png, slightly more modern would be an SVG</li> <li>an inline SVG as an additional element</li> <li>the <a href="https://css-tricks.com/the-shapes-of-css/#triangle-down-shape">border trick</a> to create a triangle</li> </ul> <p>SVG may feel like the optimal solution, however when used as a <code>background-image</code> it loses the ability to act like an icon in the sense of not being able to alter its properties such as fill color without redefining it entirely. This means we cannot use our CSS custom variable.</p> <p>Placing an SVG inline solves the <code>fill</code> color issue, however it means including one more element every time a <code>&lt;select&gt;</code> is defined.</p> <p>With <code>clip-path</code>, we get a crisp, scalable arrow &quot;graphic&quot; that <em>feels like</em> an SVG but with the benefits of being able to use our custom variable and being contained in the style vs. the HTML markup.</p> <p>To create the arrow, we will define it as an <code>::after</code> pseudo-element.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select::after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.8em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--select-arrow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>100% 0%<span class="token punctuation">,</span> 0 0%<span class="token punctuation">,</span> 50% 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>clip-path</code> syntax is a little strange, and since it's not really the focus of this article, I recommend the following resources:</p> <ul> <li>Colby Fayock explans the syntax with an example in <a href="https://egghead.io/lessons/css-add-a-cutout-notch-to-an-html-element-with-a-css-polygon-clip-path">this egghead video</a></li> <li><a href="https://bennettfeely.com/clippy/">Clippy</a> is an online tool that allows you to select a shape and adjust the points while dynamically generating the <code>clip-path</code> CSS</li> </ul> <p>If you're following along, you may have noticed the arrow is not appearing despite defining <code>width</code> and <code>height</code>. When inspected, its found that the <code>::after</code> is not actually being allowed it's width.</p> <p>We will resolve this by updating our <code>.select</code> to use CSS grid layout.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This lets the arrow appear by essentially extending it a display value akin to &quot;block&quot;.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ztphtxex4qzjywvowoiv.png" alt="clip-path arrow now appears below the native select" /></p> <p>At this stage we can verify that we have indeed created a triangle.</p> <p>To fix the alignment, we'll use my favorite CSS grid hack (old hat to you if you've read a few articles around here!).</p> <p>Old CSS solution: <code>position: absolute</code> New CSS solution: A single <code>grid-template-areas</code> to contain them all</p> <p>First we'll define our area, then define that the <code>select</code> and the <code>::after</code> both use it. The name is scoped to the element its created for, and we'll keep it easy by calling it &quot;select&quot;:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"select"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">select, .select:after </span><span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> select<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which gives us an overlap of the arrow above the native select due to stacking context via source order:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/esdzj5jsvmddxefyy9jn.png" alt="preview of the updated arrow position above the native select" /></p> <p>We can now use grid properties to finalize the alignment of each element:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.select:after </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">justify-self</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Ta-da!</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/na2vayzyanyrfx2gui9c.png" alt="finished initial styles for the custom select" /></p> <div class="heading-wrapper h3"> <h3 id="focus-state"><code>:focus</code> State</h3> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#focus-state" aria-labelledby="focus-state"><span hidden="">#</span></a></div> <p>Oh yeah - remember how we removed the <code>outline</code>? We need to resolve the missing <code>:focus</code> state from dropping that.</p> <p>There is an upcoming property we could use called <code>:focus-within</code> but it's still best to include a polyfill for it at this time.</p> <p>For this tutorial, we'll use an alternate method that achieves the same result, just a bit heftier.</p> <p>Unfortunately, this means we need to add one more element into the DOM.</p> <p>After the native select element, as the last child within <code>.select</code>, add:</p> <pre class="language-html"><code class="language-html"><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>focus<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span></code></pre> <p>Why after? Because since this is a pure CSS solution, placing it after the native select means we can alter it when the <code>select</code> is focused by use of the adjacent sibling selector - <code>+</code>.</p> <p>This allows us to create the following rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select:focus + .focus </span><span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">bottom</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--select-focus<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You may be wondering why we're back to <code>position: absolute</code> after just learning the previous <code>grid-area</code> hack.</p> <p>The reason is to avoid recalculating adjustments based on padding. If you try it on your own, you'll see that even setting <code>width</code> and <code>height</code> to 100% still makes it sit within the padding.</p> <p>The job <code>position: absolute</code> does best is matching the size of an element. We're pulling it an extra pixel in each direction to make sure it overlaps the border property.</p> <p>But, we need to make one more addition to <code>.select</code> to ensure that it's relative to our select by - well, <code>position: relative</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span></code></pre> <p>And here's our custom select all together as seen in Chrome:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/cykucssuq6909qwrgnk1.gif" alt="gif demo of focusing and selecting an option in the custom select" /></p> <div class="heading-wrapper h2"> <h2 id="multiple-select">Multiple Select</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#multiple-select" aria-labelledby="multiple-select"><span hidden="">#</span></a></div> <p>Selects come in a second flavor, which allows a user to select more than one option. From the HTML perspective, this simply means add the <code>multiple</code> attribute, but we'll also add a class to help create style adjustments called <code>select--multiple</code>:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multi-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Multiple Select<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <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>select select--multiple<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</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>multi-select<span class="token punctuation">"</span></span> <span class="token attr-name">multiple</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 4<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 5<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option length<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Option that has too long of a value to fit <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span> <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>focus<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>And looking at it, we can see it's inherited most of our styles favorably, except we don't need the arrow in this view.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/gs313hkhignewqzgd661.png" alt="multiple select with inherited styles as previously defined" /></p> <p>This is a quick fix to adjust our selector that defines the arrow. We use <code>:not()</code> to exclude our newly defined class:</p> <pre class="language-scss"><code class="language-scss">.<span class="token property">select</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>.select--multiple<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token punctuation">:</span>after</code></pre> <p>We have a couple of minor adjustments to make for the multiple select, the first is removing padding that was previously added to make room for the arrow:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] </span><span class="token punctuation">{</span> <span class="token property">padding-right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>By default, options with a long value will overflow visible area and be clipped, but I found that the main browsers allow the wrapping to be overridden if you desire:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] option </span><span class="token punctuation">{</span> <span class="token property">white-space</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Optionally, we can set a <code>height</code> on the select to bring a bit more reliable cross-browser behavior. Through testing this, I learned that Chrome and Firefox will show a partial option, but Safari will completely hide an option that is not able to be fully in view.</p> <p>The height must be set directly on the native select. Given our other styles, the value <code>6rem</code> will be able to show 3 options:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 6rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>At this point, due to current browser support, we have made as much adjustments as we are able.</p> <blockquote> <p>The <code>:selected</code> state of the <code>options</code> is fairly customizable in Chrome, somewhat in Firefox, and not at all in Safari. See the <a href="https://moderncss.dev/custom-select-styles-with-pure-css/#demo">CodePen demo</a> for a section that can be uncommented to preview this.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="disabled-styles"><code>:disabled</code> Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#disabled-styles" aria-labelledby="disabled-styles"><span hidden="">#</span></a></div> <p>While I would advocate for simply not showing disabled controls, we should prepare the styles for that state just to cover our bases.</p> <p>To emphasis the disabled state, we want to apply a greyed background. But since we've set background styles on <code>.select</code> and there isn't a <code>:parent</code> selector, we need to create one last class to handle for this state:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select--disabled </span><span class="token punctuation">{</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to top<span class="token punctuation">,</span> #ddd<span class="token punctuation">,</span> #eee 33%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here we've updated the cursor as an extra hint that the field cannot be interacted with, and updated the background values we previously set to be white to now be more grey for the disabled state.</p> <p>This results in the following appearances:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/iwmnikta0b915i1a0d11.png" alt="previous of the disabled state styles for both single and multiple select" /></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>You can test it for yourself, but here's a preview of the full solution across (from left) the Firefox, Chrome, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ct4v1q5jbxznf02zjiy9.png" alt="final styled selects across the browsers mentioned" /></p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="MWyyYNz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Pure CSS Custom Checkbox Style 2020-07-27T00:00:00Z https://moderncss.dev/pure-css-custom-checkbox-style/ <p>We'll create custom, cross-browser, theme-able, scalable checkboxes in pure CSS with the following:</p> <ul> <li><code>currentColor</code> and CSS custom properties for theme-ability</li> <li><code>em</code> units for relative sizing</li> <li>use of pseudo elements for the <code>:checked</code> indicator</li> <li>CSS grid layout to align the input and label</li> </ul> <p>Many of the concepts here overlap with our <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">custom styled radio buttons</a> from episode 18, with the addition of styling for the <code>:disabled</code> state</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="checkbox-html">Checkbox HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#checkbox-html" aria-labelledby="checkbox-html"><span hidden="">#</span></a></div> <p>In the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a> article, we explored the two valid ways to markup input fields. Much like then, we will select the method where the label wraps the input.</p> <p>Here's our base HTML for testing both an unchecked and checked state:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Checkbox <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkbox-checked<span class="token punctuation">"</span></span> <span class="token attr-name">checked</span> <span class="token punctuation">/></span></span> Checkbox - checked <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-checkboxes">Common Issues With Native Checkboxes</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#common-issues-with-native-checkboxes" aria-labelledby="common-issues-with-native-checkboxes"><span hidden="">#</span></a></div> <p>As with the radio buttons, the checkbox appearance varies across browsers.</p> <p>Here's the base styles across (in order from left) Chrome, Firefox, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hd8limqlb7v3wqf197e3.png" alt="default checkboxes in Chrome, Firefox, and Safari" /></p> <p>Also like with radio buttons, the checkbox doesn't scale along with the <code>font-size</code>.</p> <p>Our solution will accomplish the following goals:</p> <ul> <li>scale with the <code>font-size</code> provided to the label</li> <li>gain the same color as provided to the label for ease of theme-ability</li> <li>achieve a consistent, cross-browser design style, including <code>:focus</code> state</li> <li>maintain keyboard and color contrast accessibility</li> </ul> <blockquote> <p>Our styles will begin with the same variable and reset as used for the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#theme-variable-and-box-sizing-reset">radio buttons</a></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="label-styles">Label Styles</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#label-styles" aria-labelledby="label-styles"><span hidden="">#</span></a></div> <p>Our label uses the class of <code>.form-control</code>. The base styles we'll include here are font styles. Recall from earlier that the <code>font-size</code> will not yet have an effect on the visual size of the checkbox <code>input</code>.</p> <details open=""> <summary>CSS for ".form-control font styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control</span> <span class="token punctuation">{</span> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-632 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-632"> <input type="checkbox" name="checkbox" /> Checkbox </label> <label class="form-control-632"> <input type="checkbox" name="checkbox-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>We're using an abnormally large <code>font-size</code> just to emphasize the visual changes for purposes of the tutorial demo.</p> <p>Our label is also the layout container for our design, and we're going to set it up to use CSS grid layout to take advantage of <code>gap</code>.</p> <details open=""> <summary>CSS for ".form-control grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 1em auto<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-122 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-122"> <input type="checkbox" name="checkbox-layout" /> Checkbox </label> <label class="form-control-122"> <input type="checkbox" name="checkbox-layout-checked" checked="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="custom-checkbox-style">Custom Checkbox Style</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#custom-checkbox-style" aria-labelledby="custom-checkbox-style"><span hidden="">#</span></a></div> <p>Alright, now we'll get into restyling the checkbox to be our custom control.</p> <blockquote> <p>The original version of this tutorial demonstrated use of extra elements to achieve the desired effect. Thanks to improved support of <code>appearance: none</code> and with appreciation to <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">Scott O'Hara's post on styling radio buttons and checkboxes</a>, we can rely on pseudo elements instead!</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="step-1-hide-the-native-checkbox-input">Step 1: Hide the Native Checkbox Input</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-1-hide-the-native-checkbox-input" aria-labelledby="step-1-hide-the-native-checkbox-input"><span hidden="">#</span></a></div> <p>We need to reset the native checkbox input styles, but keep it interactive to enable proper keyboard interaction and also to maintain access to the <code>:focus</code> state.</p> <p>To accomplish this, we only need to set <code>appearance: none</code>. This removes nearly all inherited browser styles <em>and</em> <strong>gives us access to styling the input's pseudo elements</strong>. Notice we have two additional properties to complete the reset.</p> <details open=""> <summary>CSS for "hiding the native checkbox input"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* Add if not using autoprefixer */</span> <span class="token property">-webkit-appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* For iOS &lt; 15 to remove gradient background */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token comment">/* Not removed via appearance */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-990 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-990 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-990"> <input type="checkbox" name="checkbox-hiding" /> Checkbox </label> <label class="form-control-990"> <input type="checkbox" name="checkbox-hiding-checked" checked="" /> Checkbox - checked </label> </div> </div> <blockquote> <p><strong>Worried about support</strong>? This combination of using <code>appearance: none</code> and the ability to style the input's pseudo elements has been supported since 2017 in Chrome, Safari, and Firefox, and in Edge since their switch to Chromium in May 2020.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="step-2-custom-unchecked-checkbox-styles">Step 2: Custom Unchecked Checkbox Styles</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-2-custom-unchecked-checkbox-styles" aria-labelledby="step-2-custom-unchecked-checkbox-styles"><span hidden="">#</span></a></div> <p>For our custom checkbox, we'll update box styles on the base input element. This includes inheriting the font styles to ensure the use of <code>em</code> produces the desired sizing outcome, as well as using <code>currentColor</code> to inherit any update on the label's color.</p> <p>We use <code>em</code> for the <code>width</code>, <code>height</code>, and <code>border-width</code> value to maintain the relative appearance. We're also customizing the <code>border-radius</code> with another <code>em</code> relative style.</p> <details open=""> <summary>CSS for "custom unchecked checkbox styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>-0.075em<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token selector">.form-control + .form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-394 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-394 + .form-control-394 { margin-top: 1em; } .form-control-394 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-394"> <input type="checkbox" name="checkbox-unchecked" /> Checkbox </label> <label class="form-control-394"> <input type="checkbox" name="checkbox-unchecked-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>Our style updates includes a rule to give some space between our checkboxes by applying <code>margin-top</code> with the help of the <a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">adjacent sibling combinator</a>.</p> <p>Finally, as discussed in our <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio button tutorial</a>, we do a small adjustment on the label vs. checkbox alignment using a <code>transform</code> to nudge it up half the width of the border.</p> <div class="heading-wrapper h3"> <h3 id="step-3-styling-checked-vs-unchecked-state">Step 3: Styling <code>:checked</code> vs Unchecked State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-3-styling-checked-vs-unchecked-state" aria-labelledby="step-3-styling-checked-vs-unchecked-state"><span hidden="">#</span></a></div> <p>To prepare for the incoming pseudo element, we first need to change the display behavior of the input to use grid.</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is the quickest way to align the <code>:before</code> to the horizontal and vertical center of our custom control.</p> <p>It's now time to bring in our <code>::before</code> pseudo element which will be styled in order to represent the <code>:checked</code> state. We create the <code>:before</code> element, including a transition and using transform hide it with <code>scale(0)</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>box-shadow</code> instead of <code>background-color</code> will enable the state of the radio to be visible when printed (h/t <a href="https://dev.to/alvaromontoro/comment/1214h">Alvaro Montoro</a>).</p> <p>Finally, when the <code>input</code> is <code>:checked</code>, we make it visible with <code>scale(1)</code> with a nicely animated result thanks to the <code>transition</code>. Be sure to change the checkbox state to see the animation!</p> <details open=""> <summary>CSS for ":checked state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]:checked::before</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-728 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-728 + .form-control-728 { margin-top: 1em; } .form-control-728 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-728 input[type="checkbox"]::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); } .form-control-728 input[type="checkbox"]:checked::before { transform: scale(1) !important; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-728"> <input type="checkbox" name="checkbox-checked-state" /> Checkbox </label> <label class="form-control-728"> <input type="checkbox" name="checkbox-checked-state-checked" checked="" /> Checkbox - checked </label> </div> </div> <h4>High Contrast Themes and Forced Colors</h4> <p>As reviewed in the radio buttons tutorial, one more state we need to ensure our radio responds to is what you may hear referred to as <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">&quot;Windows High Contrast Mode&quot; (WHCM)</a>. In this mode, the user's operating system swaps out color-related properties for a reduced palette which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors">an incoming part of the CSS spec called &quot;forced-colors&quot;</a>.</p> <p>Since <code>box-shadow</code> is removed, we'll ensure the <code>:checked</code> state is visible by providing a <code>background-color</code>, which is normally removed in forced-colors mode, but will be retained if we use one of the defined forced colors. In this case, we're selecting <code>CanvasText</code> which will match the regular body text color.</p> <p>Due to the style stacking order, our <code>box-shadow</code> that we've themed for use in regular mode is actually visuallly placed <em>over</em> the <code>background-color</code>, meaning we can use both without any further modifications.</p> <details open=""> <summary>CSS for "supporting forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token comment">/* Windows High Contrast Mode */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-259 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-259 + .form-control-259 { margin-top: 1em; } .form-control-259 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-259 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-259 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-259"> <input type="checkbox" name="checkbox-forced-colors" /> Checkbox </label> <label class="form-control-259"> <input type="checkbox" name="checkbox-forced-colors-checked" checked="" /> Checkbox - checked </label> </div> </div> <h4>Creating the &quot;Checkmark&quot; Shape</h4> <p>Right now, the filled-in state is OK, but it would be ideal to have it shaped as a checkmark to match the more expected pattern.</p> <p>We have a few options here, such as bringing in an SVG as a background image. However, that solution means losing access to CSS custom properties which we are relying on to &quot;theme&quot; our inputs.</p> <p>Instead, we'll re-shape the default box by using the <code>clip-path</code> property. This property allows us to treat the pseudo element's box similar to a vector element being reshaped with the pen tool. We define coordinates to redraw the shape between. You can use <a href="https://bennettfeely.com/clippy/">this handy clip-path generator</a> to draw your own shapes or instantly pick up common ones. We also use <code>clip-path</code> to create a <a href="https://moderncss.dev/custom-select-styles-with-pure-css/">custom select dropdown arrow</a>.</p> <p>As a matter of preference, I also alter the <code>transform-origin</code> to use a value of <code>bottom left</code> instead of the default of <code>center</code> to mimic a sort of &quot;checking in&quot; animation.</p> <details open=""> <summary>CSS for "creating a checkmark with clip-path"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> bottom left<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>14% 44%<span class="token punctuation">,</span> 0 65%<span class="token punctuation">,</span> 50% 100%<span class="token punctuation">,</span> 100% 16%<span class="token punctuation">,</span> 80% 0%<span class="token punctuation">,</span> 43% 62%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-698 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-698 + .form-control-698 { margin-top: 1em; } .form-control-698 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-698 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-698 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-698"> <input type="checkbox" name="checkbox-checkmark" /> Checkbox </label> <label class="form-control-698"> <input type="checkbox" name="checkbox-checkmark-checked" checked="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h3"> <h3 id="step-4-the-focus-state">Step 4: The <code>:focus</code> state</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-4-the-focus-state" aria-labelledby="step-4-the-focus-state"><span hidden="">#</span></a></div> <p>In the earlier version of this tutorial, we used <code>box-shadow</code>, but now we have two improved features for the humble <code>outline</code>. First, we can use <code>outline-offset</code> to create a bit of space between the input and the outline. Second, evergreen browsers now support <code>outline</code> following <code>border-radius</code>!</p> <blockquote> <p>Remember: <code>:focus</code> is a temporary state, but it's very important that it is highly visible to ensure the accessibility of your form controls and other interactive elements.</p> </blockquote> <details open=""> <summary>CSS for ":focus state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</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> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</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> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-895 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-895 + .form-control-895 { margin-top: 1em; } .form-control-895 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-895 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-895 input:checked::before { transform: scale(1); } .form-control-895 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-895"> <input type="checkbox" name="checkbox-focus" /> Checkbox </label> <label class="form-control-895"> <input type="checkbox" name="checkbox-focus-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>This concludes our critical styles for the checkbox. If you're interested in an additional method to style the label, check out the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio button tutorial</a> to learn how to use <code>:focus-within</code>.</p> <div class="heading-wrapper h3"> <h3 id="styles-for-disabled-checkboxes">Styles For <code>:disabled</code> Checkboxes</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#styles-for-disabled-checkboxes" aria-labelledby="styles-for-disabled-checkboxes"><span hidden="">#</span></a></div> <p>One step not present in the radio buttons tutorial was styling for the <code>:disabled</code> state.</p> <p>This will follow a similar pattern as for our previous states, with the change here mostly being to update the color to a grey. We first re-assign the main <code>--form-control-color</code> to the new <code>--form-control-disabled</code> variable. Then, set the <code>color</code> property to use the disabled color.</p> <details open=""> <summary>CSS for ":disabled state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--form-control-disabled</span><span class="token punctuation">:</span> #959495<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]:disabled</span> <span class="token punctuation">{</span> <span class="token property">--form-control-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-539 { --form-control-color: rebeccapurple; --form-control-disabled: #959495; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-539 + .form-control-539 { margin-top: 1em; } .form-control-539 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-539 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-539 input:checked::before { transform: scale(1); } .form-control-539 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } .form-control-539 input:disabled { --form-control-color: var(--form-control-disabled); color: var(--form-control-disabled); cursor: not-allowed; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-539"> <input type="checkbox" name="checkbox-disabled" disabled="" /> Checkbox </label> <label class="form-control-539"> <input type="checkbox" name="checkbox-disabled-checked" checked="" disabled="" /> Checkbox - checked </label> </div> </div> <p>We've also updated to set the cursor to <code>not-allowed</code> as an additional visual cue that these inputs are not presently interactive.</p> <p>But we've hit a snag. Since the label is the parent element, we don't currently have a way in CSS alone to style it based on the <code>:disabled</code> state.</p> <p>For a CSS-only solution, we need to create an add an extra class to the label when it is known that the checkbox is disabled. Since this state can't be changed by the user, this will generally be an acceptable additional step.</p> <details open=""> <summary>CSS for ":disabled state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control--disabled</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-398 { --form-control-color: rebeccapurple; --form-control-disabled: #959495; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-398 + .form-control-398 { margin-top: 1em; } .form-control-398 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-398 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-398 input:checked::before { transform: scale(1); } .form-control-398 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } .form-control-398 input:disabled { --form-control-color: var(--form-control-disabled); color: var(--form-control-disabled); cursor: not-allowed; } .form-control--disabled-398 { color: var(--form-control-disabled); cursor: not-allowed; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-398 form-control--disabled-398"> <input type="checkbox" name="checkbox-disabled-label" disabled="" /> Checkbox </label> <label class="form-control-398 form-control--disabled-398"> <input type="checkbox" name="checkbox-disabled-label-checked" checked="" disabled="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Here's a demo that includes the <code>:disabled</code> styles, and also shows how the power of CSS variables + the use of <code>currentColor</code> means we can re-theme an individual checkbox with a simple inline style. This is very useful for things like a quick change to an error state.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="RwrOygP" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Pure CSS Custom Styled Radio Buttons 2020-07-14T00:00:00Z https://moderncss.dev/pure-css-custom-styled-radio-buttons/ <p>Using a combination of the following properties, we can create custom, accessible, cross-browser, theme-able, scalable radio buttons in pure CSS:</p> <ul> <li><code>currentColor</code> for theme-ability</li> <li><code>em</code> units for relative sizing</li> <li><code>appearance: none</code> for full restyling access</li> <li>CSS grid layout to align the input and label</li> </ul> <p><strong>Head's up</strong>: A lot of these styles overlap with the episode on <a href="https://moderncss.dev/pure-css-custom-checkbox-style">custom checkbox styles</a> which you might be interested in reading next!</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="radio-button-html">Radio Button HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#radio-button-html" aria-labelledby="radio-button-html"><span hidden="">#</span></a></div> <p>There are two appropriate ways to layout radio buttons in HTML.</p> <p>The first wraps the <code>input</code> within the <code>label</code>. This implicitly associates the label with the input that its labeling, and also increases the hit area to select the radio.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio label text <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>The second is to have the <code>input</code> and <code>label</code> be siblings and use the <code>for</code> attribute set to the value of the radio's <code>id</code> to create the association.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></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>radio1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Radio label text<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>Our technique will work with either setup, although we're going to select the wrapping label method to prevent including an extra div.</p> <p>The base HTML for our demo including classes and two radios - necessary to test <code>:checked</code> vs. un-checked states - is the following:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio - checked <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>For groups of radio buttons, it is also necessary to provide the same <code>name</code> attribute.</p> <p>Here's how the native HTML elements in Chrome appear:</p> <p><img src="https://moderncss.dev/img/posts/18/radio-chrome-default.png" alt="native radio buttons in Chrome" /></p> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-radio-buttons">Common Issues with Native Radio Buttons</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#common-issues-with-native-radio-buttons" aria-labelledby="common-issues-with-native-radio-buttons"><span hidden="">#</span></a></div> <p>The primary issue that causes developers to seek a custom styling solution for radio buttons is the variance in their appearance between browsers which is increased when including mobile browsers as well.</p> <p>As an example, here are radio buttons as shown on Mac versions of Firefox (left), Chrome (middle), and Safari (right):</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bnce8hn7xmnc9fibmspf.png" alt="radio buttons in Firefox, Chrome, Safari" /></p> <p>Our solution will accomplish the following goals:</p> <ul> <li>scale with the <code>font-size</code> provided to the <code>label</code></li> <li>gain the same color as provided to the label for ease of theme-ability</li> <li>achieve a consistent, cross-browser design style, including <code>:focus</code> state</li> <li>maintain keyboard and color contrast accessibility</li> </ul> <blockquote> <p>If your primary goal is modifying the <code>:checked</code> state color, you may be interested in learning more about <a href="https://www.smashingmagazine.com/2021/09/simplifying-form-styles-accent-color/">the upcoming <code>accent-color</code> property</a> from Michelle Barker's overview.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="theme-variable-and-box-sizing-reset">Theme Variable and <code>box-sizing</code> Reset</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#theme-variable-and-box-sizing-reset" aria-labelledby="theme-variable-and-box-sizing-reset"><span hidden="">#</span></a></div> <p>There are two base CSS rules that must be placed first in our cascade.</p> <p>First, we create a custom variable called <code>--color</code> which we will use as a simple way to easily theme our radio buttons.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--form-control-color</span><span class="token punctuation">:</span> rebeccapurple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, we use the universal selector to reset the <code>box-sizing</code> method used to <code>border-box</code>. This means that padding and border will be included in the calculation of any elements computed final size instead of increasing the computed size beyond any set dimensions.</p> <pre class="language-css"><code class="language-css"><span class="token selector">*, *:before, *:after</span> <span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="label-styles">Label Styles</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#label-styles" aria-labelledby="label-styles"><span hidden="">#</span></a></div> <p>Our label uses the class of <code>.form-control</code>. The base styles we'll include here are font styles. Recall from earlier that the <code>font-size</code> will not yet have an effect on the visual size of the radio <code>input</code>.</p> <details open=""> <summary>CSS for ".form-control font styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control</span> <span class="token punctuation">{</span> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-57 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-57"> <input type="radio" name="radio" /> Radio </label> <label class="form-control-57"> <input type="radio" name="radio" checked="" /> Radio - checked </label> </div> </div> <p>We're using an abnormally large <code>font-size</code> just to emphasize the visual changes for purposes of the tutorial demo.</p> <p>Our label is also the layout container for our design, and we're going to set it up to use CSS grid layout to take advantage of <code>gap</code>.</p> <details open=""> <summary>CSS for ".form-control grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 1em auto<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-334 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-334"> <input type="radio" name="radio-form-control-334" /> Radio </label> <label class="form-control-334"> <input type="radio" name="radio-form-control-334" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="custom-radio-button-style">Custom Radio Button Style</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#custom-radio-button-style" aria-labelledby="custom-radio-button-style"><span hidden="">#</span></a></div> <p>Ok, this is the part you came here for!</p> <blockquote> <p>The original version of this tutorial demonstrated use of extra elements to achieve the desired effect. Thanks to improved support of <code>appearance: none</code> and with appreciation to <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">Scott O'Hara's post on styling radio buttons and checkboxes</a>, we can rely on pseudo elements instead!</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="step-1-hide-the-native-radio-input">Step 1: Hide the Native Radio Input</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-1-hide-the-native-radio-input" aria-labelledby="step-1-hide-the-native-radio-input"><span hidden="">#</span></a></div> <p>We need to hide the native radio input, but keep it technically accessible to enable proper keyboard interaction and also to maintain access to the <code>:focus</code> state.</p> <p>To accomplish this, we only need to set <code>appearance: none</code>. This removes nearly all inherited browser styles <em>and</em> <strong>gives us access to styling the input's pseudo elements</strong>. Notice we have two additional properties to complete the reset.</p> <details open=""> <summary>CSS for "hiding the native radio input"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* Add if not using autoprefixer */</span> <span class="token property">-webkit-appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* For iOS &lt; 15 to remove gradient background */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token comment">/* Not removed via appearance */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-817 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-817 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-817"> <input type="radio" name="radio-hide-native" /> Radio </label> <label class="form-control-817"> <input type="radio" name="radio-hide-native" checked="" /> Radio - checked </label> </div> </div> <blockquote> <p><strong>Worried about support</strong>? This combination of using <code>appearance: none</code> and the ability to style the input's pseudo elements has been supported since 2017 in Chrome, Safari, and Firefox, and in Edge since their switch to Chromium in May 2020.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="step-2-custom-unchecked-radio-styles">Step 2: Custom Unchecked Radio Styles</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-2-custom-unchecked-radio-styles" aria-labelledby="step-2-custom-unchecked-radio-styles"><span hidden="">#</span></a></div> <p>For our custom radio, we'll update box styles on the base input element. This includes inheriting the font styles to ensure the use of <code>em</code> produces the desired sizing outcome, as well as using <code>currentColor</code> to inherit any update on the label's color.</p> <p>We use <code>em</code> for the <code>width</code>, <code>height</code>, and <code>border-width</code> value to maintain the relative appearance. Good ole <code>border-radius: 50%</code> finishes the expected appearance by rendering the element as a circle.</p> <details open=""> <summary>CSS for "custom unchecked radio styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token selector">.form-control + .form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-977 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-977 + .form-control-977 { margin-top: 1em; } .form-control-977 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-977"> <input type="radio" name="radio-unchecked-styles" /> Radio </label> <label class="form-control-977"> <input type="radio" name="radio-unchecked-styles" checked="" /> Radio - checked </label> </div> </div> <p>Finally, we slid in a little style to provide some space between our radios by applying <code>margin-top</code> with the help of the <a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">adjacent sibling combinator</a>;</p> <div class="heading-wrapper h3"> <h3 id="step-3-improve-input-vs-label-alignment">Step 3: Improve Input vs. Label Alignment</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-3-improve-input-vs-label-alignment" aria-labelledby="step-3-improve-input-vs-label-alignment"><span hidden="">#</span></a></div> <p>If you've worked with grid or flexbox, your instinct right now might be to apply <code>align-items: center</code> to optically tune the alignment of the input in relation to the label text.</p> <p>But what if the label is long enough to become broken across multiple lines? In that case, alignment along horizontal center may be undesirable.</p> <p>Instead, let's make adjustments so the input stays horizontally centered in relation to the first line of the label text.</p> <p>On our input, we'll use <code>transform</code> to nudge the element up. This is a bit of a magic number, but as a starting point this value is half the size of the applied border.</p> <details open=""> <summary>CSS for "improve input vs. label alignment"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>-0.075em<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-352 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-352 + .form-control-352 { margin-top: 1em; } .form-control-352 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-352"> <input type="radio" name="radio-alignment" /> Radio </label> <label class="form-control-352"> <input type="radio" name="radio-alignment" checked="" /> Radio - checked </label> </div> </div> <p>And with that our alignment is complete and functional for both single-line and multi-line labels.</p> <div class="heading-wrapper h3"> <h3 id="step-4-the-checked-state">Step 4: The <code>:checked</code> State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-4-the-checked-state" aria-labelledby="step-4-the-checked-state"><span hidden="">#</span></a></div> <p>It's now time to bring in our <code>::before</code> pseudo element which will be styled in order to represent the <code>:checked</code> state.</p> <blockquote> <p>The <code>:checked</code> naming convention may be a little confusing here, but it is a CSS selector that is available for both radio buttons and checkboxes.</p> </blockquote> <p>We first need to change the display behavior of the input to use grid:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is the quickest way to align the <code>:before</code> to the horizontal and vertical center of our custom control.</p> <p>Then, we create the <code>:before</code> element, including a transition and using transform hide it with <code>scale(0)</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>box-shadow</code> instead of <code>background-color</code> will enable the state of the radio to be visible when printed (h/t <a href="https://dev.to/alvaromontoro/comment/1214h">Alvaro Montoro</a>).</p> <p>Finally, when the <code>input</code> is <code>:checked</code>, we make it visible with <code>scale(1)</code> with a nicely animated result thanks to the <code>transition</code>. Be sure to click between the radios to see the animation!</p> <details open=""> <summary>CSS for ":checked state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="radio"]:checked::before</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-679 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-679 + .form-control-679 { margin-top: 1em; } .form-control-679 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-679 input[type="radio"]::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); } .form-control-679 input[type="radio"]:checked::before { transform: scale(1) !important; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-679"> <input type="radio" name="radio-checked-state" /> Radio </label> <label class="form-control-679"> <input type="radio" name="radio-checked-state" checked="" /> Radio - checked </label> </div> </div> <h4>High Contrast Themes and Forced Colors</h4> <p>One more state we need to ensure our radio responds to is what you may hear referred to as <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">&quot;Windows High Contrast Mode&quot; (WHCM)</a>. In this mode, the user's operating system swaps out color-related properties for a reduced palette which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors">an incoming part of the CSS spec called &quot;forced-colors&quot;</a>.</p> <p>In this mode, our <code>box-shadow</code> is completely removed, leaving these users without an indicator of the checked state.</p> <p>Fortunately, resolving this involves adding just one extra property. We'll provide a <code>background-color</code>, which is normally removed in forced-colors mode, but will be retained if we use one of the defined forced colors. In this case, we're selecting <code>CanvasText</code> which will match the regular body text color.</p> <p>Due to the style stacking order, our <code>box-shadow</code> that we've themed for use in regular mode is actually visuallly placed <em>over</em> the <code>background-color</code>, meaning we can use both without any further modifications.</p> <details open=""> <summary>CSS for "supporting forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token comment">/* Windows High Contrast Mode */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-23 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-23 + .form-control-23 { margin-top: 1em; } .form-control-23 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-23 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-23 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-23"> <input type="radio" name="radio-forced-colors" /> Radio </label> <label class="form-control-23"> <input type="radio" name="radio-forced-colors" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h3"> <h3 id="step-5-the-focus-state">Step 5: The <code>:focus</code> State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-5-the-focus-state" aria-labelledby="step-5-the-focus-state"><span hidden="">#</span></a></div> <p>Depending on your browser, you may already be seeing some kind of a focus style provided as an <code>outline</code>. We'll add just a tiny bit of customization to make it match our input's color, and provide some space from the input by using <code>outline-offset</code>.</p> <p>This is a simplification from the earlier version of this tutorial which used <code>box-shadow</code>. Now, evergreen browsers all support <code>outline</code> which follows <code>border-radius</code>, removing an excuse not to just use the <code>outline</code>!</p> <blockquote> <p>Remember: <code>:focus</code> is a temporary state, but it's very important that it is highly visible to ensure the accessibility of your form controls and other interactive elements.</p> </blockquote> <details open=""> <summary>CSS for ":focus state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</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> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</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> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-403 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-403 + .form-control-403 { margin-top: 1em; } .form-control-403 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-403 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-403 input:checked::before { transform: scale(1); } .form-control-403 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-403"> <input type="radio" name="radio-focus-state" /> Radio </label> <label class="form-control-403"> <input type="radio" name="radio-focus-state" checked="" /> Radio - checked </label> </div> </div> <p>And with that, the essential styles for a custom radio button are complete! 🎉</p> <div class="heading-wrapper h2"> <h2 id="experimental-using-focus-within-to-style-the-label-text">Experimental: Using <code>:focus-within</code> to Style the Label Text</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#experimental-using-focus-within-to-style-the-label-text" aria-labelledby="experimental-using-focus-within-to-style-the-label-text"><span hidden="">#</span></a></div> <p>Since the label is not a sibling of the native input, we can't use the <code>:focus</code> state of the input to style it.</p> <p>An upcoming pseudo selector is <code>:focus-within</code>, and one feature is that it can apply styles to elements that contain an element which has received focus.</p> <blockquote> <p>The ModernCSS episode on a <a href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/">pure CSS accessible dropdown navigation menu</a> also covered <code>:focus-within</code>.</p> </blockquote> <p>For now, any critial usage of <code>:focus-within</code> requires a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a>, so the following styles should be considered an enhancement and not relied on as the only way to provide a visual indication of focus.</p> <p>We'll test for focus by adding a rule for <code>:focus-within</code> on the label (<code>.form-control</code>). This means when the native input - which is a child and therefore &quot;within&quot; the label - receives focus, we can style <em>any</em> element within the label while focus is active.</p> <details open=""> <summary>CSS for "experimental :focus-within styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control:focus-within</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-183 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-183 + .form-control-183 { margin-top: 1em; } .form-control-183:focus-within { color: var(--form-control-color); } .form-control-183 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-183 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-183 input:checked::before { transform: scale(1); } .form-control-183 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-183"> <input type="radio" name="radio-focus-within" /> Radio </label> <label class="form-control-183"> <input type="radio" name="radio-focus-within" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Here is the solution altogether in a CodePen that you can fork and experiment with further.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VweBgeZ" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>Check out the <a href="https://moderncss.dev/pure-css-custom-checkbox-style">custom checkbox styling</a> to also learn how to extend styles to the <code>:disabled</code> state, and see how to work with <code>clip-path</code> as a <code>:checked</code> indicator.</p> Announcing Style Stage: A Community CSS Showcase 2020-07-10T00:00:00Z https://moderncss.dev/announcing-style-stage-a-community-css-showcase/ <p>Dear CSS community:</p> <p>I invite you to participate in a new project where you have the opportunity to challenge both your CSS and web design skills while learning in public.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1wnz0c4yyqq03vfrhds1.png" alt="Style Stage: A modern CSS showcase styled by community contributions" /></p> <div class="heading-wrapper h2"> <h2 id="about-style-stage">About Style Stage</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#about-style-stage" aria-labelledby="about-style-stage"><span hidden="">#</span></a></div> <p><a href="https://stylestage.dev/">StyleStage.dev</a> is not just an informational landing page about the project - it is the foundational HTML which is intended to be restyled by contributors - like you!</p> <p>Style Stage started as a wild idea to reanimate the spirit of <a href="http://www.csszengarden.com/">CSS Zen Garden</a> which was created by <a href="http://daveshea.com/projects/zen/">Dave Shea</a> and that provided a demonstration of &quot;what can be accomplished through CSS-based design&quot; until submissions stopped in 2013.</p> <p>Things in CSS-land have improved a lot since then, including the available properties, the tools we have available to build with, our greater understanding of addressing accessibility concerns, and increased awareness of performance impacts.</p> <blockquote> <p>If you missed the launch live stream, here's the lightly edited <a href="https://youtu.be/O2hLsVX5eN0">full recorded Twitch broadcast</a>, or by topic in <a href="https://www.twitch.tv/collections/CZgljEORIBZLxg">the Twitch highlight collection</a>. We covered a lot of the things in this article plus more about CSS, and concluded by building a new 11ty feature ✨.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="why-do-we-need-this">Why Do We Need This?</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#why-do-we-need-this" aria-labelledby="why-do-we-need-this"><span hidden="">#</span></a></div> <p>There's a growing tendency to choose a framework when what would best serve a project is using CSS in its natural state and becoming one with the <a href="https://dev.to/5t3ph/intro-to-the-css-cascade-the-c-in-css-1kh0">cascade</a>.</p> <p>Creating your Style Stage stylesheet will challenge you to explore techniques like flexbox and grid to arrange the page, and pseudo elements to add extra content and flair. Take the opportunity to design something crazy! So far, gradients and <code>transform: skew()</code> are popular with contributors ✨ Check out the <a href="https://stylestage.dev/#about">list of other modern features</a> for inspiration of what you may like to try.</p> <p>By prohibiting access to the HTML (which is already semantic and accessible on its own), Style Stage encourages you to get creative while re-familiarizing yourself with the basics. And in this otherwise fast-paced industry, I see that as a major positive.</p> <p>Play is a powerful teacher! How far can you push the boundaries while staying accessible and performant? These are skills worth practicing that will equip you to choose the right tool for the job in future projects. Even if the right tool is a framework, you will have a deeper understanding of <em>how</em> styles you apply are working and improve your ability to customize them.</p> <p>Trust me - it feels good to say: &quot;I can do that in CSS!&quot;</p> <p>For a growing list of tips, ideas, and inspiration, <a href="https://stylestage.dev/resources/">view the resources</a>.</p> <p><a href="https://stylestage.dev/subscribe/">Subscribe to the newsletter</a> for periodic updates related to new styles and release of new features. You can also pick up the <a href="https://stylestage.dev/feed/">RSS feed</a>.</p> <div class="heading-wrapper h2"> <h2 id="how-do-i-contribute-a-stylesheet">How Do I Contribute a Stylesheet?</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#how-do-i-contribute-a-stylesheet" aria-labelledby="how-do-i-contribute-a-stylesheet"><span hidden="">#</span></a></div> <p>Review <a href="https://stylestage.dev/">StyleStage.dev</a> for expanded details, as well as the source HTML and CSS.</p> <p>By participating as a contributor, your work will be shared with your provided attribution as long as Style Stage is online, your stylesheet link and any asset links remain valid, and all contributor guidelines are adhered to.</p> <p><a href="https://stylestage.dev/#contribute">Review the steps to contribute &gt;</a></p> <div class="heading-wrapper h3"> <h3 id="guidelines-tldr">Guidelines TL;DR</h3> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#guidelines-tldr" aria-labelledby="guidelines-tldr"><span hidden="">#</span></a></div> <p>All submissions will be minified, autoprefixed, and prepended with the <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA license</a> as well as attribution using the metadata you provide. You may use any build setup you prefer, but the final submission should be the compiled, unminified CSS. You retain the copyright to original graphics and must ensure all graphics used are appropriately licensed. All asset links, including fonts, must be absolute to external resources. Stylesheets will be saved into the Github repo, and detected changes that violate the guidelines are cause for removal.</p> <p>Ensure your design is responsive, and that it passes accessible contrast (we'll be using aXe to verify). Animations should be removed via <code>prefers-reduced-motion</code>. Cutting-edge techniques should come with a fallback if needed to not severely impact the user experience. No content may be permanently hidden, and hidden items must come with an accessible viewing technique. Page load time should not exceed 3 seconds.</p> <p><a href="https://stylestage.dev/guidelines/">Review the full guidelines &gt;</a></p> <div class="heading-wrapper h2"> <h2 id="possible-future-features">(Possible) Future Features</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#possible-future-features" aria-labelledby="possible-future-features"><span hidden="">#</span></a></div> <ul> <li><strong>dark mode toggle</strong> - optionally opt into a toggle to improve the user experience by allowing them to choose which theme to display rather than relying on <code>prefers-color-scheme</code> values alone</li> <li><strong>style index preview images</strong> - to improve the experience of browsing available styles</li> </ul> <div class="heading-wrapper h2"> <h2 id="credits">Credits</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#credits" aria-labelledby="credits"><span hidden="">#</span></a></div> <p>Big thanks to <a href="https://twitter.com/hankchizljaw">Andy Bell (@hankchizljaw)</a> for his extra time reviewing the foundations of the project, and being an early promotor! 💫</p> <p>Thanks also to <a href="https://twitter.com/MiriSuzanne">Miriam Suzanne (@MiriSuzanne)</a> for some great feedback and ideas about how to evolve the project 🚀</p> <p>The project description and guidelines were made more clear by suggestions from <a href="https://twitter.com/KatieLangerman">Katie Langerman (@KatieLangerman)</a> 🙌</p> <p>And of course the original six contributors - thanks so much for helping bring this project to life by spending time within a short deadline to create your awesome stylesheets!</p> <div class="heading-wrapper h2"> <h2 id="some-stats">Some Stats</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#some-stats" aria-labelledby="some-stats"><span hidden="">#</span></a></div> <ul> <li>The idea for Style Stage arose July 2, 2020 and the project launched July 10, 2020.</li> <li>Built on my favorite static site generator, <a href="https://11ty.dev/">11ty</a> beginning from a starter I developed</li> <li>Hosted on <a href="https://www.netlify.com/">Netlify</a></li> <li>The Main Stage theme receives a 100 lighthouse and PageSpeed score, as well as a speed index of 0.502s 🙌</li> </ul> <p>Here are the original 6 contributors:</p> <ul> <li><a href="https://stylestage.dev/styles/retroish">Retroish</a> by Jean Louise Tiston</li> <li><a href="https://stylestage.dev/styles/skewten">Skewten</a> by Donnie D'Amato</li> <li><a href="https://stylestage.dev/styles/purplify-and-pastel">Purplify &amp; Pastel</a> by Dominic Duffin</li> <li><a href="https://stylestage.dev/styles/vaporwave">Vaporwave</a> by Shannon Crabill</li> <li><a href="https://stylestage.dev/styles/center-stage">Center Stage</a>by Katie Langerman</li> <li><a href="https://stylestage.dev/styles/queer-modes">Queer Modes</a> by Miriam Suzanne</li> </ul> 3 Popular Website Heroes Created With CSS Grid Layout 2020-07-02T00:00:00Z https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/ <p>This episode explores creating website heroes - aka &quot;headers&quot; - with one of my favorite ways to use CSS grid layout: by turning it into a canvas.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Support notice</strong>: The essential properties used in these techniques - <code>grid-template-areas</code> and <code>object-fit</code> - are not supported below IE 16. Good news - that still means they are about 96% supported!</p> </blockquote> <p>Inspired by my years in marketing, here are the three layouts we're going to create:</p> <p>#1: Marketing Call-to-Action (CTA) and Image</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z56fftywzc8qgq5xm00m.png" alt="preview of marketing hero" /></p> <p>#2: Text Overlay on Background Image</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f5f211a3wfy2ut8ak8xw.png" alt="preview of text overlay hero" /></p> <p>#3: Two-Column with Copy and Form</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hmsmbwiwna1zoaw5dfht.png" alt="preview of two-column hero" /></p> <div class="heading-wrapper h2"> <h2 id="base-html-and-css-grid-setup">Base HTML and CSS Grid Setup</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#base-html-and-css-grid-setup" aria-labelledby="base-html-and-css-grid-setup"><span hidden="">#</span></a></div> <p>In the not-too-distant past, the way to achieve most of these layouts required the use of <code>position: absolute</code>.</p> <p>With grid, we can upgrade from that solution and gain responsive, dynamic positioning superpowers!</p> <p>Here's our starting point for HTML:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">></span></span> <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>hero__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>You really need this product, so hurry and buy it today!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Buy Now<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://placecorgi.com/600<span class="token punctuation">"</span></span> <span class="token attr-name">alt</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 punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span></code></pre> <p>Then, we'll turn the <code>header</code> into a grid container, and create a single template area called &quot;hero&quot;:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"hero"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of the template area creates a single named grid cell. We then create a rule to assign all children of any type (thanks to the universal selector <code>*</code>) to this area:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-is-this-magic">What is this magic?</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#what-is-this-magic" aria-labelledby="what-is-this-magic"><span hidden="">#</span></a></div> <p>Using CSS grid layout template areas means that we get all the goodness of grid positioning which is a big upgrade from absolute positioning!</p> <p>This directs that all children share the same grid cell, effectively turning it into a canvas.</p> <p>We can now define items be centered or other positions relative to each other and the container <em>instead of</em> doing math to calculate percentages, or encountering media query headaches to get around absolute positioning interfering with responsive growing and shrinking of content.</p> <p>Read on to gain more context with our header examples!</p> <div class="heading-wrapper h2"> <h2 id="hero-1-marketing-call-to-action-cta-and-image">Hero #1: Marketing Call-to-Action (CTA) and Image</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-1-marketing-call-to-action-cta-and-image" aria-labelledby="hero-1-marketing-call-to-action-cta-and-image"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z56fftywzc8qgq5xm00m.png" alt="preview of marketing hero" /></p> <p>With no other styles yet in place besides our base, here's what we have: the elements are aligned top left, with the image layered over the <code>.hero__content</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dg2w0otsiww9bxpl79oq.png" alt="initial state of the base marketing hero" /></p> <p>The first thing we'll address is setting some dimension expectations on the header:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 65vh<span class="token punctuation">;</span> <span class="token property">max-height</span><span class="token punctuation">:</span> 600px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Viewport units such as <code>vh</code> are my go-to way to size heroes. This keeps them proportionate to the users viewing area by dynamically sizing them up or down depending on device size.</p> <p>We are capping this particular one to prevent the image resolution from getting too stretched by way of <code>max-height</code>, but that is optional and circumstantial to the image in use.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Next, we need to provide some direction on the <code>img</code> behavior.</p> <p>You may be wondering why we didn't use a background image. The first answer is so that the image retains its semantics including the <code>alt</code> attribute in order to be discoverable by assistive technology.</p> <p>Second, keeping it as an image allows more flexibility in how we style and position it.</p> <p>We will use <code>object-fit</code> together with <code>object-position</code> which actually makes its initial behavior very similar to that of a background image:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">object-position</span><span class="token punctuation">:</span> 5vw -5vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>65vh<span class="token punctuation">,</span> 600px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 60%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>height: min(65vh, 600px)</code> is important, because it directs it to fill the height of the <code>header</code> based on the &quot;minimum&quot; of either of those values, which comes from the heights we set on the base <code>header</code>. After giving explicit dimension parameters, <code>object-fit</code> takes over and scales the image contents to &quot;cover&quot; the dimensions including the <code>width: 60%</code>.</p> <blockquote> <p><strong>New to</strong> <code>object-fit</code>? Check out <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">episode 3 on responsive images</a> or <a href="https://moderncss.dev/animated-image-gallery-captions-with-bonus-ken-burns-effect/">episode 6 on animated image captions</a> for more examples.</p> </blockquote> <p>Finally, we will add <code>justify-self</code> to the <code>img</code> to define that it should be placed at the <code>end</code> of the container - our first dip into the magic of using grid for this solution:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's our progress:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/m4aq7uhkgnfeld50esns.png" alt="marketing hero progress with image styles" /></p> <p>Now for the <code>.hero__content</code>, the first improvement is to give it a width definition, and also give it some space from the viewport edge:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 35%<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Since our <code>img</code> is allowed a width of 60%, we don't want our combined <code>margin</code> and <code>width</code> to exceed 40% in order to avoid overlap.</p> <p>We also provided a <code>min-width</code> to keep a reasonable amount of space for the content as the viewport shrinks.</p> <p>Now we can again leverage the use of grid, and return to our <code>header</code> rule to add an alignment property:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This vertically aligns the content with the image. Since the image is set to 100% of the <code>header</code> height, optically this vertically centers the content, resulting in our desktop-ready hero:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mywia1t4o2qsd5wwedqk.png" alt="marketing hero desktop finalized" /></p> <p>In order for this to continue working on the smallest screens, we need a couple tweaks.</p> <p>First, we'll default the image width to 80% and wrap the 60% reduction in a media query. We'll also add a transition just to smooth it between viewport resizes:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">width</span><span class="token punctuation">:</span> 80%<span class="token punctuation">;</span> <span class="token comment">// &lt; update</span> <span class="token property">transition</span><span class="token punctuation">:</span> 180ms width ease-in<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 60rem<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 60%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Then on the content, we'll use a bit of trickery to set the background to an alpha of the hero background so it's only visible once it begins to overlap the image, and include an update on the margin, some padding, and a bit of <code>border-radius</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem 0 1rem 5%<span class="token punctuation">;</span> <span class="token comment">// &lt; update</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token function">mix</span><span class="token punctuation">(</span>#fff<span class="token punctuation">,</span> <span class="token variable">$primary</span><span class="token punctuation">,</span> 97%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5rem 0.5rem 0.5rem 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We <em>did</em> have to add one little <code>z-index</code> there to bring it above the <code>img</code>, but it wasn't too painful! 😊</p> <p>Here's the final mobile-sized viewport result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/za98lm6kb4vr7aniumrj.png" alt="marketing hero mobile finalized" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-1">Summary of Techniques in Hero #1</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-1" aria-labelledby="summary-of-techniques-in-hero-1"><span hidden="">#</span></a></div> <ul> <li><code>object-fit</code> used to control <code>img</code> size</li> <li><code>align-items: center</code> used to vertically align the grid children</li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-1-demo">Hero #1 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-1-demo" aria-labelledby="hero-1-demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="eYJGJVg" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="hero-2-text-overlay-on-background-image">Hero #2: Text Overlay on Background Image</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-2-text-overlay-on-background-image" aria-labelledby="hero-2-text-overlay-on-background-image"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f5f211a3wfy2ut8ak8xw.png" alt="preview of text overlay hero" /></p> <p>For this version with our base HTML and CSS styles, the image completely obscures the content since it's a jpg and therefore has no alpha.</p> <p>So step 1: bring the content above the image:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, we'll define header dimensions:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 60vh<span class="token punctuation">;</span> <span class="token property">max-height</span><span class="token punctuation">:</span> 600px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And again, we'll use <code>object-fit</code> to control our <code>img</code>. The difference this time is we want it to span 100% width <em>and</em> height so it has full coverage over the header:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>60vh<span class="token punctuation">,</span> 600px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Before we show a progress shot, let's adjust the alignment of the grid children:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here's the result so far:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/35l09zkti0xhmsxaolf9.png" alt="progress of hero #2" /></p> <p>It's quite apparent that the contrast of the text is not sufficient over the background image. One common way to both add an extra touch of branding and also aid in addressing contrast issues is to apply a color screen to an image.</p> <p>Here's our slight hack to accomplish this - first, the <code>header</code> receives a <code>background-color</code> that includes alpha transparency:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$primary</span></span><span class="token punctuation">:</span> #3c87b3<span class="token punctuation">;</span> <span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token variable">$primary</span><span class="token punctuation">,</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we direct the image to slip behind the header with <code>z-index</code>. In my testing, this still keeps the <code>img</code> discoverable with assistive tech, but reach out if you know of an issue!</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">z-index</span><span class="token punctuation">:</span> -1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Resulting in the following:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/zszictap7muu2qlvy67r.png" alt="base of hero two completed" /></p> <p>To demonstrate a bit more about what is possible thanks to using grid, let's create a <code>:before</code> and <code>:after</code> pseudo-element on the <code>header</code> to hold an SVG pattern.</p> <p>The important thing to include is to also assign the pseudo-elements to <code>grid-area: hero</code>. Otherwise, they would slot in as new &quot;rows&quot; according to default grid flow, which would break our canvas.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>::before </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 50vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 50vmin<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>-10%<span class="token punctuation">,</span> -10%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url">url</span><span class="token punctuation">(</span><span class="token string">"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{svgColor($support)}' fill-opacity='0.6' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>::after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 60vmin<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>20%<span class="token punctuation">,</span> 40%<span class="token punctuation">)</span> <span class="token function">rotate</span><span class="token punctuation">(</span>45deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url">url</span><span class="token punctuation">(</span><span class="token string">"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{svgColor($support)}' fill-opacity='0.6' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And due to the <code>place-items: center</code> definition, here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/j15c0cwopacooq9w2bgx.png" alt="hero with pseudo-elements added" /></p> <p>The first issue to resolve is the overflow, which we'll fix with:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, grid offers <code>self</code> properties to direct that specific item can reposition itself, which breaks it from the grid parent definition. So we'll update our pseudo-elements accordingly:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>::before </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-self</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>::after </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-self</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And with that, we've completed hero 2! Test out the demo to see that the small viewport version continues to work well:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/urq97jy17bipfg5du00t.png" alt="completed hero #2" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-2">Summary of Techniques in Hero #2</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-2" aria-labelledby="summary-of-techniques-in-hero-2"><span hidden="">#</span></a></div> <ul> <li>created a color screen over the <code>img</code> by defining <code>background-color</code> of the <code>header</code> with <code>rgba</code> and adding <code>z-index: -1</code> to the <code>img</code> to slide it behind the <code>header</code></li> <li>used pseudo-elements for additional design flair, and positioned them separately from the parent grid definition with <code>place-self</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-2-demo">Hero #2 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-2-demo" aria-labelledby="hero-2-demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="GRoMxor" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="hero-3-two-column-with-copy-and-form">Hero #3: Two-Column with Copy and Form</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-3-two-column-with-copy-and-form" aria-labelledby="hero-3-two-column-with-copy-and-form"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hmsmbwiwna1zoaw5dfht.png" alt="preview of two-column hero" /></p> <p>For this third example, our base HTML changes a bit to add in the form. We also include a wrapper around the main content which we'll explain soon:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">></span></span> <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>hero__wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>hero__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>You really need this product, so hurry and buy it today!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>hero__form<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Subscribe to Our Updates<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Enter your email:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>email<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></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>email<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</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>button<span class="token punctuation">"</span></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>submit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Subscribe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span></code></pre> <p>And here's our starting appearance, given use of things we've already learned: the <code>header</code> SVG pattern pseudo-element has already used <code>place-self: end</code>, the form styles are already in-tact (spoiler: that is using grid too!), and overflow is also already being controlled:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/q4k3y3y16vim19lpxqsa.png" alt="starting hero #3 appearance" /></p> <p>Let's start to fix this by beginning our <code>.hero__wrapper</code> class. An important update is to set its width to <code>100vw</code> so that as a containing element it spans the header entirely. We'll also go ahead and create it as a grid container:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100vw<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, it's time to define the grid columns. We'll use my favorite technique which is already featured in multiple episodes for intrinsically responsive grid columns:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>30ch<span class="token punctuation">,</span> auto<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><strong>Learn more</strong> about this technique in episode 8: <a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/">Solutions to Replace the 12-Column Grid</a></p> </blockquote> <p>We've used <code>auto</code> for the max-allowed width instead of <code>1fr</code> since we do not want equal columns, but rather for the columns to expand proportionately to their relative size. This is for a bit better visual balance between the text content and the form, and can be adjusted to taste. If you desire equal-width columns, use <code>1fr</code> instead of <code>auto</code>.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/l755c2tx2e1dv5tyi6gb.png" alt="hero #3 with grid-template-columns applied" /></p> <p>Let's talk a minute about that bottom gradient border - how is it being positioned?</p> <p>It is the <code>:after</code> element on the <code>header</code> and is the primary reason we are using a wrapper around the main header content. It is being positioned with <code>place-self: end</code>, and its width is due to the natural stretch behavior. Check the demo to see how minimal its style are.</p> <p>Ok, now we need some additional spacing around the content. In the other heroes, we applied a <code>height</code> but this doesn't quite cover our use case here because on smaller viewports the form and content will vertically stack.</p> <p>Instead, this is a better job for good ole <code>padding</code>. We'll place it on <code>.hero__wrapper</code> so as not to affect the position of the SVG pattern or gradient border:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10vmin 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of the viewport unit <code>vmin</code> for the top and bottom padding means that the smaller of &quot;view-width&quot; or &quot;view-height&quot; will be used for that value. The benefit here is helping ensure the hero doesn't cover the entire screen of smaller viewports, which may make it seem like there isn't additional page content. This is because in that case the &quot;veiw-width&quot; will be used making it a smaller value versus on larger, desktop viewports where it will use &quot;view-height&quot; and be a greater value.</p> <p>To complete the large viewport appearance, we will add two positioning values to the wrapper:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Where <code>align-items</code> provides vertical alignment, and <code>justify-content</code> provides horizontal alignment.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/rolfl5if4yy3hcs6yji7.png" alt="completed large viewport appearance for hero #3" /></p> <p>On smaller viewports, our only adjustment is to ensure that the content remains legible over the SVG pattern. We'll use a similar technique to hero #1:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$primary</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> 90%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jx9m8ftlhd2gwzix3yp3.png" alt="hero #3 with mobile adjusted styles" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-3">Summary of Techniques in Hero #3</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-3" aria-labelledby="summary-of-techniques-in-hero-3"><span hidden="">#</span></a></div> <ul> <li>use of a wrapper to provide a secondary grid layout for content versus <code>header</code> design elements</li> <li>creation of auto-width columns with <code>grid-template-columns</code></li> <li>leveraging <code>vmin</code> to minimize padding on smaller viewports and increase it for larger viewports</li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-3-demo">Hero #3 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-3-demo" aria-labelledby="hero-3-demo"><span hidden="">#</span></a></div> <p><em>Bonus</em>: use of <code>clamp</code> to shrink the paragraph copy proportionate to the viewport size in order to reduce it for smaller viewports.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="oNboWLP" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> 3 CSS Grid Techniques to Make You a Grid Convert 2020-06-27T00:00:00Z https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/ <p>CSS grid layout can feel daunting. In fact, I avoided it for several years and was a <em>diehard</em> flexbox fan.</p> <p>Then I found the following 3 powerful properties/techniques in grid that completely changed my tune.</p> <p>Spoiler, here's a tweet with all of them. Keep reading to learn a bit more!</p> <p><a href="https://twitter.com/5t3ph/status/1276898582113681409" class="button button--small">View tweet</a></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="1-switch-the-grid-flow-axis">1: Switch the Grid Flow Axis</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#1-switch-the-grid-flow-axis" aria-labelledby="1-switch-the-grid-flow-axis"><span hidden="">#</span></a></div> <p>I first desired this behavior when I wanted X-axis alignment of variable width items, and also desired to leverage <code>gap</code>.</p> <div class="heading-wrapper h3"> <h3 id="the-code">The Code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code" aria-labelledby="the-code"><span hidden="">#</span></a></div> <pre class="language-css"><code class="language-css"><span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does" aria-labelledby="what-it-does"><span hidden="">#</span></a></div> <p>Default grid flow is oriented to &quot;row&quot; layout, which is complementary to block layout, where items flow down the page along the Y-axis.</p> <p>This switches that default behavior to &quot;column&quot; which means items default to flowing along the X-axis.</p> <div class="heading-wrapper h3"> <h3 id="things-to-note">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note" aria-labelledby="things-to-note"><span hidden="">#</span></a></div> <ul> <li>items will take as much room as needed to contain their content <em>up until</em> the max width of the container, at which point text will break to new lines</li> <li>there is a risk of overflow because of lack of &quot;wrapping&quot; behavior in grid, which means assigning this property will flow things along the X-axis into infinity <ul> <li>this can be solved by only applying this behavior above a certain viewport width via a media query</li> </ul> </li> </ul> <blockquote> <p><strong>Note</strong>: once flexbox <code>gap</code> is fully supported, it will likely be the better method for this outcome due to also having wrapping behavior</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use" aria-labelledby="when-to-use"><span hidden="">#</span></a></div> <p>For short content where variable widths are desirable, such as a navbar or list of icons, and when wrapping either isn't a concern or a media query can be used to flip this property.</p> <div class="heading-wrapper h2"> <h2 id="2-xy-center-anything">2. XY Center Anything</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#2-xy-center-anything" aria-labelledby="2-xy-center-anything"><span hidden="">#</span></a></div> <p>Literally the thing everyone makes fun of when CSS comes up as a topic:</p> <blockquote> <p>&quot;How do you center a div?&quot;</p> </blockquote> <p>Grid has the easiest answer!</p> <blockquote> <p>Psst - interested in <em>lots</em> of solutions to centering? Check out the &quot;<a href="https://moderncss.dev/complete-guide-to-centering-in-css/">The Complete Guide to Centering in CSS</a>&quot;</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="the-code-1">The code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code-1" aria-labelledby="the-code-1"><span hidden="">#</span></a></div> <pre class="language-css"><code class="language-css"><span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does-1">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does-1" aria-labelledby="what-it-does-1"><span hidden="">#</span></a></div> <p>Centers any child content both vertically (Y) and horizontally (X) 🙌</p> <div class="heading-wrapper h3"> <h3 id="things-to-note-1">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note-1" aria-labelledby="things-to-note-1"><span hidden="">#</span></a></div> <ul> <li>there are some <a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-grid-solution">gotchas</a> related to the behavior assigned to the children</li> <li>the visual appearance may be only that it's centered horizontally if the container's height doesn't exceed the height of the children</li> </ul> <div class="heading-wrapper h3"> <h3 id="when-to-use-1">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use-1" aria-labelledby="when-to-use-1"><span hidden="">#</span></a></div> <p>Anytime you want to center something vertically and horizontally.</p> <div class="heading-wrapper h2"> <h2 id="3-intrinsically-responsive-grid-columns">3. Intrinsically Responsive Grid Columns</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#3-intrinsically-responsive-grid-columns" aria-labelledby="3-intrinsically-responsive-grid-columns"><span hidden="">#</span></a></div> <div class="heading-wrapper h3"> <h3 id="the-code-2">The code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code-2" aria-labelledby="the-code-2"><span hidden="">#</span></a></div> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--grid-col-breakpoint</span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.grid-columns </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--grid-col-breakpoint<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does-2">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does-2" aria-labelledby="what-it-does-2"><span hidden="">#</span></a></div> <p>The unique-to-grid functions of <code>repeat()</code> and <code>minmax()</code> along with the <code>auto-fit</code> keyword work together to create an outcome where immediate children become equal-width, responsive columns.</p> <p>As the grid container resizes, upon hitting the supplied value for <code>--grid-col-breakpoint</code>, the columns begin to drop to a new virtual row.</p> <p>Use of the <code>--grid-col-breakpoint</code> CSS variables allows altering this &quot;breakpoint&quot; via inline style to accommodate various content within a layout or across components with only a single class.</p> <div class="heading-wrapper h3"> <h3 id="things-to-note-2">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note-2" aria-labelledby="things-to-note-2"><span hidden="">#</span></a></div> <ul> <li>columns will always be equal width, growing and shrinking to remain equal as the container also flexes in size</li> <li>it's possible for orphan columns to exist at certain container widths</li> </ul> <blockquote> <p>Check out a more comprehensive explanation of what's happening in &quot;<a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid">Solutions to Replace the 12-Column Grid</a>&quot;</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use-2">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use-2" aria-labelledby="when-to-use-2"><span hidden="">#</span></a></div> <p>If you're in need of an intrinsically responsive grid with equal-width columns.</p> <p>This behavior is also one of two viable &quot;container query&quot; methods that we currently have available since it responds to container width instead of being controlled by media queries.</p> <blockquote> <p>Learn more about the idea of using <a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#grid-solution">grid for container queries</a> &gt;</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>This CodePen demonstrates all 3 techniques:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="gOPxxNO" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>Check out my <a href="https://5t3ph.dev/egghead">egghead lessons</a> which further explore these grid techniques</p> </blockquote> Expanded Use of `box-shadow` and `border-radius` 2020-06-21T00:00:00Z https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/ <p>This episode will explore expanded usage of <code>box-shadow</code> and <code>border-radius</code> and conclude with a landing page demo using these properties to enhance the image presentation.</p> <p>You will learn:</p> <ul> <li>the expanded syntax of <code>border-radius</code>, and when to use which type of units to set values</li> <li>how to create multiple <code>box-shadow</code> layers</li> <li>about the <code>box-shadow</code> value <code>inset</code></li> <li>how to &quot;hack&quot; <code>box-shadow</code> with <code>inset</code> to apply popular image filter techniques</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="overview-of-border-radius">Overview of <code>border-radius</code></h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#overview-of-border-radius" aria-labelledby="overview-of-border-radius"><span hidden="">#</span></a></div> <p>Kids these days will never have to deal with creating a gif for each corner of a div that you want to appear rounded 😂 Truly, the release and eventual support of <code>border-radius</code> was one of the most significant milestones in CSS.</p> <p>For a refresher, here's how it's often used:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></code></pre> <p>Which for a square element will result in a circle appearance.</p> <p>Or scale it back for just a slight roundedness to otherwise sharply square corners, such as for a button or card where you might use:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span></code></pre> <p>One way to begin to take this a bit further is to use two values, where the first value sets top-left and bottom-right, and the second value sets top-right and bottom-left:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 20% 50%<span class="token punctuation">;</span></code></pre> <p>Whiiiich... kind of looks like a lemon?</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1f7t6sztody2bx49wyru.png" alt="example of two value border-radius" /></p> <p>Now, given the same values but a different size div those results will vary quite widely:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/a23blvdcovhwkg3990l3.png" alt="same border-radius value applied to various size divs" /></p> <p>We can also set all four corners:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* top-left | top-right | bottom-right | bottom-left */</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 3vw 4vw 8vw 2vw<span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/987sbc9xlb92hmvu88ai.png" alt="example with all four corners set on border-radius" /></p> <p>But wait - there's more!</p> <p>There's a super uber expanded syntax which allows you to define both the horizontal and vertical radius each corner is rounded by. Whereas the default is rounded very circularly, adding the additional radius alters the &quot;clipping&quot; that occurs to produce the corner, allowing the trendy &quot;blob&quot; shapes:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/17ejae2bvf386oml7fm2.png" alt="&quot;blob&quot; shape with border-radius expanded syntax" /></p> <blockquote> <p>Check out this <a href="https://codepen.io/jh3y/pen/XWmvwYg">border-radius playground</a> by Jhey with a config panel and live preview to generate expanded syntax <code>border-radius</code> styles.</p> </blockquote> <p>There are a few more ways to define the syntax, you can check those out on <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius">the MDN docs</a>.</p> <div class="heading-wrapper h3"> <h3 id="units-for-border-radius">Units for <code>border-radius</code></h3> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#units-for-border-radius" aria-labelledby="units-for-border-radius"><span hidden="">#</span></a></div> <p>Notice that we've used a few different units: <code>%</code>, <code>px</code>, and <code>vw</code> which is the &quot;viewport width&quot; viewport unit.</p> <p><strong>Percentage</strong> values are relative to the size of the element which means less predictability if the element is expected to be various sizes. For the <code>50%</code> example, once the element is no longer square it begins to appear more elliptical.</p> <p><strong>Absolute units</strong> such as <code>px</code> or <code>rem</code> are preferred when you want consistent results not based on the element but based on perhaps design specs.</p> <p><strong>Relative units</strong> such as viewport units or <code>em</code> can be beneficial if you want consistency but also an element of scale:</p> <ul> <li>viewport units will <em>compute</em> to be larger on &quot;desktop&quot; and smaller on &quot;mobile&quot; but remain consistent in rounded appearance</li> <li><code>em</code> will vary based on <code>font-size</code>, resulting in more rounded corners on elements with larger fonts and less rounded corners on elements with smaller fonts</li> </ul> <div class="heading-wrapper h2"> <h2 id="overview-of-box-shadow">Overview of <code>box-shadow</code></h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#overview-of-box-shadow" aria-labelledby="overview-of-box-shadow"><span hidden="">#</span></a></div> <p>Personally, upon it reaching decent support, the thing that made <code>box-shadow</code> the most exciting for me was for popping models off the page in a far more native fashion 🙏</p> <p>And for awhile, they were <em>mission critical</em> for what we called &quot;skeumorphic design&quot;. And they made a bit of a comeback over the past year for &quot;neumorphic design&quot;.</p> <blockquote> <p>Were those new terms to you? Here's <a href="https://uxdesign.cc/neumorphism-in-user-interfaces-b47cef3bf3a6">a nice overview</a> comparing each with examples by UX Collective.</p> </blockquote> <p>But what I usually use <code>box-shadow</code> for these days is just a subtle hint at elevation for things like buttons or cards:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* offset-x | offset-y | blur-radius | spread-radius | color */</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 3px 4px 5px 0px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.38<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/gngoroh4e552tie3giuf.png" alt="demo of basic box-shadow" /></p> <p>It is acceptable to omit the <code>blur-radius</code> and <code>spread-radius</code>, which leads to a sharper shadow due to loss of blur. The shadow will also not extend beyond the offset values since the spread is intended to scale beyond the element's dimensions.</p> <p>In the above example, we essentially gave the shadow a &quot;light source&quot; that was slightly above and left of the element which &quot;cast&quot; the shadow slightly right and below.</p> <p>Instead, you can set the offsets to 0 for a shadow that is equal around the element, although at least <code>blur-radius</code> is required. If <code>spread-radius</code> is also supplied, that will apply scale to the shadow to extend it beyond the element's dimensions:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0.25em 0.25em <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5i9920x1vi8s1ncllkdv.png" alt="no offset box-shadow" /></p> <p>I enjoy using <code>box-shadow</code> to provide a custom visual <code>:focus</code> state on buttons. Unlike <code>border</code>, <code>box-shadow</code> does <em>not</em> alter the elements computed dimensions, meaning adding or removing it does not cause either it or elements around it to shift from reflow. Check out <a href="https://moderncss.dev/css-button-styling-guide/">the episode on buttons</a> to see that method.</p> <p>A unique feature of <code>box-shadow</code> is the ability to apply multiples:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 2px 4px 0 4px yellowgreen<span class="token punctuation">,</span> 4px 8px 0 8px pink<span class="token punctuation">,</span> 8px 10px 0 12px navy<span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hzpsvyd4u3qoo8oob6d5.png" alt="multiple box-shadow values" /></p> <p>The &quot;stacking order&quot; is such that the first set is &quot;on top&quot;, or visually closest to the element, and down from there. That's why the <code>spread-radius</code> has to be increased, else the &quot;lower&quot; layers would not be visible (except where the offset doesn't overlap).</p> <p>We can also flip <code>box-shadow</code> to the <em>inside</em> of the element by adding the <code>inset</code> value:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 12px 4px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>These values will apply a very literal &quot;inset&quot; shadow appearance, where the element appears &quot;sunk&quot; into the page:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/pg2npjg4kh7rgoj5zsto.png" alt="inset box-shadow" /></p> <div class="heading-wrapper h3"> <h3 id="box-shadow-hacks-for-images"><code>box-shadow</code> Hacks for Images</h3> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#box-shadow-hacks-for-images" aria-labelledby="box-shadow-hacks-for-images"><span hidden="">#</span></a></div> <p>Two alternate ways I like to use <code>inset</code> shadows are for images.</p> <p><strong>The key is in the stacking order</strong> and the fact <code>box-shadow</code> is placed <em>above</em> images applied via <code>background-image</code>.</p> <h4>Vignette</h4> <p>The first is as a &quot;vignette&quot; which is a photography technique where the edges of the photo softly fade into the background. This helps highlight the subject of the photo particularly if the subject is centrally located.</p> <p>However, the <code>inset</code> property cannot be directly used on an <code>&lt;img/&gt;</code> since it is considered an &quot;empty&quot; element, not a container element.</p> <p>Going with a <code>background-image</code> instead, the following CSS is applied to the container:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.vignette-container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 4vmin 3vmin <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a comparison where the left image has the vignette applied and the right does not.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hotopx3kxs7c5gj3zvj7.png" alt="two images where left image has vignette applied" /></p> <p>We used <code>vmin</code> units so that the <code>box-shadow</code> would scale relative to the overall image size which was also set in <code>vmin</code>. This is due to the inability for <code>box-shadow</code> to be set using percent, which makes it a little difficult to set values relative to the element. So the hack within a hack is to use viewport units to set expected size of the element to have more predictable results for the <code>box-shadow</code> across viewport sizes.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Image Color Screen</h4> <p>I found this technique when I was using <code>background-image</code> but also wanted a color &quot;screen&quot; - which means needing to place a translucent color fill over the image. This is a helpful technique to help defend the contrast of any text placed over background images, and also to create visual consistency among an otherwise unrelated set of images.</p> <p>My previous solution - used for many years - was an extra absolutely positioned <code>:before</code> on the containing element with the screen color applied there as <code>background-color</code>.</p> <p>Here's the new <code>inset</code> <code>box-shadow</code> solution:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 0 100vmax <span class="token function">rgba</span><span class="token punctuation">(</span>102<span class="token punctuation">,</span> 76<span class="token punctuation">,</span> 202<span class="token punctuation">,</span> 0.65<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And a comparison with the screen on the left and without on the right:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nv0in4wlzaobgfqwa7sz.png" alt="screen comparison images" /></p> <p>Did you catch the hack? I mentioned that percentages aren't allowed - but since viewport units are, we set the <code>spread</code> to <code>100vmax</code> which is <em>likely</em> to cover any element unless your element is greater than <em>double</em> the max-width or max-height of the viewport.</p> <p>Hold up - why <em>double</em>? Because the <code>spread</code> is created from <em>all sides</em> of the element so it's at least double the <code>vmax</code> value. You can test it out by using the above value and setting your container to at least <code>width: 100vw; height: 100vh</code> and see at what point the middle of the container is not covered.</p> <p>If you're using it for something more than a hero image, for example, just increase as needed, such as to <code>200vmax</code>.</p> <p><em>If anyone is aware of taking performance hits for this, let me know!</em></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>The demo goes a bit further and shows how to use <code>object-fit</code> on images to make them <em>behave</em> like <code>background-image</code> while still retaining the semantics of an image, which is great when use of the <code>alt</code> property is necessary (spoiler: you should go this route most of the time for accessibility).</p> <p>In addition, the <code>h1</code> headline has <code>text-shadow</code> applied, which is animated on <code>:hover</code> of the <code>header</code>. The main difference between <code>box-shadow</code> and <code>text-shadow</code> is that <code>text-shadow</code> does not have a <code>spread</code> property.</p> <p>It also combines techniques by showing <code>border-radius</code> in conjunction with <code>box-shadow</code> for the content images. And, the content images show how <code>box-shadow</code> can be animated by pulling back the vignette fade on <code>:hover</code> for a highlighting effect.</p> <p>The extra trick on the <code>box-shadow</code> animation is re-supplying the <code>inset</code> values to ensure the pull-back of the fade uses our translucent black instead of white. This is because <code>box-shadow</code> in most browsers (except Safari) defaults to the value of the current color if not explicitly supplied, and the list items have set <code>color: #fff</code>.</p> <p><strong>Bonus</strong>: My favorite &quot;<code>position:absolute</code> killer&quot; using CSS grid and assigning all elements to the same <code>grid-area</code> - maybe a future episode and/or <a href="https://5t3ph.dev/egghead">egghead video</a> will cover that 😉</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="ExPZNRZ" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Container Query Solutions with CSS Grid and Flexbox 2020-06-11T00:00:00Z https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/ <p>True container queries are a much asked for CSS feature that would be a complement to media queries but be placed on container elements instead of the viewport.</p> <blockquote> <p><strong>Experimental CSS container queries are here!</strong> <a href="https://css.oddbird.net/rwd/query/explainer/">Here's Miriam Suzanne's container queries proposal explainer</a> which has an experimental prototype in Chrome Canary. After you download Canary, visit <code>chrome://flags</code> to search for and enable container queries. For more info, review my <a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">primer on container queries</a>.</p> </blockquote> <p>Using grid and flexbox, we can create styles that respond to <em>container</em> and <em>content</em> widths and overcome some of the pain points that container queries are proposed to resolve.</p> <p>We'll cover:</p> <ul> <li>⏸ Use of CSS grid layout and flexbox to achieve container dependent adjustments from equal-width columns to row layout</li> <li>🏆 The &quot;holy grail&quot; solution to variable-width breakpoint columns</li> <li>🚀 Implementing CSS custom variables to make the solutions as scalable as possible</li> </ul> <p><a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#solutions">Skip to the solutions &gt;</a></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="understanding-the-problem">Understanding the Problem</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#understanding-the-problem" aria-labelledby="understanding-the-problem"><span hidden="">#</span></a></div> <p>When Ethan Marcotte introduced the concept of <a href="https://alistapart.com/article/responsive-web-design/">Responsive Design</a>, we became trained to adjust elements on the page across viewport sizes by using media queries.</p> <p>I am a huge fan of Ethan's and recall reading that article within weeks of publish. It was easily one of the most transformative articles for the web.</p> <blockquote> <p>P.S. - Responsive Design recently turned 10! <a href="https://ethanmarcotte.com/wrote/responsive-design-at-10/">Read Ethan's reflection &gt;</a></p> </blockquote> <p>Ten years ago, media queries made sense because we've always been bound to the tools available at the time.</p> <p>But there remained one area where CSS didn't quite have an answer: how do you respond to changes within an individual container on the page instead of the whole viewport?</p> <p>This gave rise to the 12-column grid and related frameworks such as Bootstrap as an intermediary solution of applying width adjustments on the fly without writing an every growing wall of one-off media queries.</p> <p>And those addressed a lot of common frustrations, and almost got us there.</p> <p>As a veteran of marketing website development, the biggest downside of relying on grid frameworks was still being bound to the tool. We would <em>rarely</em> intentionally design outside of a 12-col grid because of the cost of overhead in developing additional styles and wrangling custom media queries.</p> <p>Too much blah blah blah for ya? Let's see <a href="https://getbootstrap.com/docs/4.5/layout/grid/#how-it-works">an example from Bootstrap docs</a>:</p> <pre class="language-html"><code class="language-html"><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>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>The key here is the <code>col-sm</code> classes which are required to switch from &quot;equal width columns&quot; above a set &quot;small&quot; viewport width (540px for Bootstrap) to full-width on even smaller viewports.</p> <p>But - what if these columns were <em>within</em> another column?</p> <p><strong>This is where we hit our issue</strong>: The <code>col-sm</code> columns won't go full-width until the <em>viewport</em> is reduced in size. Which means, the nested columns could be extremely narrow. To resolve this, you may have to add a bunch of extra utility classes to switch, switch, and possibly even switch again.</p> <p>Wouldn't it be nice if those columns were somehow aware of their content, and break based on a minimum <em>content</em> width rather than rely on <em>viewport</em> width?</p> <p>Welcome to the need for container queries 🙌</p> <blockquote> <p><strong>Note</strong>: The comparable solutions presented for grid vs. flexbox, unfortunately, are restricted to equal column widths, but this still helps reduce the need for media queries and fill in the gap for behavior to be based on container vs. viewport width. <a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#holy-grail-variable-width-breakpoint-columns">Skip to the &quot;holy grail&quot; solution</a> for variable width columns.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="solutions">Solutions</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#solutions" aria-labelledby="solutions"><span hidden="">#</span></a></div> <p>We will look at how to handle container queries with grid and flexbox as well as discuss the pros and cons of each.</p> <p><strong>Preview</strong></p> <p>To further provide context for what we're looking to achieve, here's the final result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/msf5471wc2brbd5t9cio.gif" alt="demo of dashboard with container queries" /></p> <p>Using only <em>three</em> classes and <em>three</em> CSS <code>vars()</code> - one of each for grid and each of the flexbox solutions - we have made the &quot;cards&quot; responsively switch from 1-3 across, <em>and</em> the card content switch from column to row layout with no media queries in sight 😎</p> <div class="heading-wrapper h3"> <h3 id="grid-solution">Grid Solution</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#grid-solution" aria-labelledby="grid-solution"><span hidden="">#</span></a></div> <blockquote> <p><strong>New to CSS grid layout</strong>? We're going to revisit a method that I've also described in <a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/">Solutions to Replace the 12-Column Grid</a> and that is explored in two of my <a href="https://5t3ph.dev/egghead">egghead videos</a> related to responsive grids.</p> </blockquote> <p>We'll begin by creating the <code>.grid</code> class, setting the display, and adding a modest gap value:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>After that, we just need one line to initiate our container query magic:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>20ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>repeat</code> function will apply the defined column behavior to all columns that exist. Right away, this makes it scalable to grow to any number of columns that could be created from any type of content.</p> <p>Then, instead of an absolute number, we use the <code>auto-fit</code> value which is responsible for ensuring the columns stay equal-width by stretching columns to fill any available space.</p> <p>After that, we use <code>minmax()</code> to set the minimum allowed column width, and then use <code>1fr</code> as the max which ensures the content fills the column as much as room allows.</p> <p><code>minmax()</code> and in particular the <code>20ch</code> is where we essentially have defined what in media query land would be our &quot;breakpoint&quot;.</p> <p>The <code>ch</code> unit is equal to the <code>0</code> (zero) character of the current font, which makes this extra sensitive to the current content.</p> <p>You could certainly swap to <code>rem</code> to prevent the computed value from changing as fonts change. However, in the demo this value technically does use the <code>font-size</code> applied to <code>body</code> which would be equivalent to <code>1rem</code> if you haven't changed it. That's because it's placed on the <code>ul</code> and not typography elements, so the <code>ul</code> defaults to inheriting <code>font</code> properties from the <code>body</code>.</p> <p>The only unit you should <em>not</em> use would be <code>%</code> as that would prevent the columns from ever collapsing.</p> <p>Cool, that takes care of the &quot;cards&quot;:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f68jidz6v92fvisk5ram.gif" alt="demo of responsive grids columns" /></p> <h4>Grid Container Content</h4> <p>Now to handle for the card content which we would also like to allow to be displayed in columns if room allows. But, the &quot;breakpoint&quot; should be smaller.</p> <p>At first, you may reach for a utility class. In fact, I did too.</p> <p>But modern CSS gives us a more flexible way: custom variables.</p> <p>First, we have to set our initial variable value, which is assigned to <code>:root</code>. The value we are setting is the <code>min</code> part of <code>minmax</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--grid-min</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And then update our rule accordingly:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--grid-min<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>We can then add our <code>.grid</code> class to the <code>li</code> which is our &quot;card&quot; container, and then use inline style to modify the <code>--grid-min</code> value:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>card grid<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--grid-min</span><span class="token punctuation">:</span> 15ch</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Jujubes soufflé cake tootsie roll sesame snaps cheesecake bonbon.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Halvah bear claw cheesecake. Icing lemon drops chupa chups pudding tiramisu. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></code></pre> <p>Resulting in our final grid solution. Notice how the card content independently adjusts from column to row layout as the card container narrows or widens.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ntxgbnub2i5dkgvkwjwq.gif" alt="demo of final grid container query solution" /></p> <p><img src="https://media.giphy.com/media/NiOPyn6a7tV3q/giphy.gif" alt="woman crying and saying &quot;it's so beautiful&quot;" /></p> <h4>Why Can't Grid Do Variable Width Columns?</h4> <p>What is the barrier preventing this solution handling variable width columns while maintaining the &quot;container query&quot; benefits of breaking to row layout without media queries?</p> <p>The closest we can get to variable width columns with grid in the easiest way possible is to remove our previous work, and instead use:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span></code></pre> <p>Which flips the default axis and by default the created columns are indeed variable width.</p> <p>However, this can't ever break down because there is no <code>wrap</code> property in grid. This means you will likely encounter overflow, which can be acceptable for predictably short content. It can also be placed within a media query to only trigger that behavior above a certain viewport width - which again, is opposite to the goal of &quot;container queries&quot;.</p> <p>I was very hopeful that our solution could be extended to this use case with:</p> <pre class="language-html"><code class="language-html">style="--grid-min: min-content;"</code></pre> <p>Which would compute to updating our property definition to:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>min-content<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>In theory, this seems like a near-perfect solve. Typically <code>min-content</code> means the content would be allowed to shrink to the <em>minimum</em> width required to hold the content (in a text block, this essentially means shrinking as far as the longest word).</p> <p>Unfortunately, <a href="https://drafts.csswg.org/css-grid/#repeat-syntax">the <code>repeat</code> spec</a> specifically prohibits this behavior:</p> <blockquote> <p>Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.</p> </blockquote> <p>Where <code>min-content</code> is considered to be one of the &quot;intrinsic sizes&quot;. Bummer.</p> <p>Read on to learn how flexbox can provide this behavior!</p> <div class="heading-wrapper h3"> <h3 id="flexbox-solution-1">Flexbox Solution #1</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#flexbox-solution-1" aria-labelledby="flexbox-solution-1"><span hidden="">#</span></a></div> <p>The first flexbox solution I'm going to describe is an example of a technique created by Heydon Pickering called the &quot;<a href="https://heydonworks.com/article/the-flexbox-holy-albatross-reincarnated/">Flexbox Albatross</a>&quot;.</p> <p>Let's begin our rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flexbox </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The notable thing here is ensuring <code>flex-wrap: wrap</code> is set, else the &quot;breakpoint&quot; effect would never actually occur.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Now, a big difference between flexbox and grid layout is that the sizing behavior of the flex children is <em>not</em> set on the parent.</p> <p>To make our rule the most flexible, we will use the child combinator - <code>&gt;</code> - in addition to the universal sector - <code>*</code> - to begin a rule that will be applied to immediate flex children of any element type.</p> <p>Using Sass, we can neatly nest this under the previous properties:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flexbox </span><span class="token punctuation">{</span> <span class="token comment">// ...existing rules</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// flex children rules</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Here's where the &quot;Flexbox Albatross&quot; magic happens. Let's add the rules and then discuss.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">flex-grow</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span>35rem <span class="token operator">-</span> 100%<span class="token punctuation">)</span> <span class="token operator">*</span> 999<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><code>flex-grow: 1</code> ensures that the column will fill as much space as the algorithm and other property values will allow.</p> <p>The <code>flex-basis</code> rule performs some math magic with the CSS property <code>calc</code> that essentially leads to the element being at minimum <code>35rem</code> and below that minimum expanding to <code>100%</code>.</p> <p>The result is equal-width columns up until the minimum acceptable width.</p> <blockquote> <p>Unfortunately, <code>calc</code> does not allow the <code>ch</code> value which takes away a bit of the ability to visualize when the column will break. In this demo, <code>35rem</code> was found to be <em>nearly</em> equivalent to <code>20ch</code> in the given font and size.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="creating-gap">Creating <code>gap</code></h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#creating-gap" aria-labelledby="creating-gap"><span hidden="">#</span></a></div> <p>As of writing, the flexbox <code>gap</code> property is gaining support but it's not quite reliably available yet.</p> <p>We will adjust our rules to use margin as a polyfill for now.</p> <blockquote> <p>Did you know: margins <em>do not</em> collapse on flexbox or grid children, so any supplied value will compound between children.</p> </blockquote> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem -0.5rem<span class="token punctuation">;</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>These rules add <code>.5rem</code> around each child, the outer portions of which is negated with a negative margin on the <code>.flex</code> parent.</p> <h4>Adjusting Breakpoint</h4> <p>Unlike grid, this base solution means that all columns will &quot;break&quot; at the same time:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/x41918h42a1y04js0dl2.gif" alt="demo of flexbox albatross" /></p> <p>That is, until we add our friend CSS variables ✨</p> <p>Let's add the variable and update <code>flex-basis</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Update on `:root`</span> <span class="token property">--flex-min</span><span class="token punctuation">:</span> 35rem<span class="token punctuation">;</span> <span class="token comment">// Update in `.flexbox`</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--flex-min<span class="token punctuation">)</span> <span class="token operator">-</span> 100%<span class="token punctuation">)</span> <span class="token operator">*</span> 999<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Now, we'll update it on the middle &quot;card&quot;:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>card<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 50rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></code></pre> <p>Annndddd - on resize, wait a minute - all three cards break at the same time, just earlier than before 🤔</p> <p>What's going on?</p> <p><code>flex-basis: 1</code> + the number of items is to blame.</p> <p>Once the middle card drops, the other two cards expand full-width thanks to <code>flex-basis: 1</code>.</p> <p>If we move our <code>--flex-min</code> adjustment to the <em>first</em> or _last card, then the remaining two cards keep their smaller &quot;breakpoint&quot;.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/heagdzygy7dg661oye98.gif" alt="demo of last card breaking at the adjusted min width" /></p> <p><img src="https://media.giphy.com/media/WqX0mS1ZEtxTcI8bw6/giphy.gif" alt="little girl rolling her eyes and waving her hands in a &quot;whatever&quot; gesture" /></p> <h4>Flexbox Container Content</h4> <p>Ok, now let's address the same idea of paragraph content that switches from column to row layout.</p> <p>With our <code>--flex-min</code> variable already in place, we have what we need.</p> <p>However, with the &quot;gotcha&quot; we just experienced, we will need to add a nested wrapper around the flex children's content:</p> <pre class="language-html"><code class="language-html"><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>flex<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 18rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Jujubes soufflé cake tootsie roll sesame snaps cheesecake bonbon.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Halvah bear claw cheesecake. Icing lemon drops chupa chups pudding tiramisu. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>This essentially resets the context so it can't affect the parent containers. A minor annoyance compared to grid, but achieves nearly identical functionality:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v5hqhgt9yz4ijrvnif4l.gif" alt="demo of flexbox content container queries" /></p> <div class="heading-wrapper h3"> <h3 id="flexbox-solution-2">Flexbox Solution #2</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#flexbox-solution-2" aria-labelledby="flexbox-solution-2"><span hidden="">#</span></a></div> <p><em>If</em> you will not need multiple unique breakpoints for the flexbox items, we can instead use what Una named the &quot;<a href="https://1linelayouts.glitch.me/">Deconstructed Pancake</a>&quot; to uniformly apply breakpoints with only <code>flex-basis</code>.</p> <p>Benefits of this technique over the Flexbox Albatross:</p> <ul> <li>the <code>flex-basis</code> breakpoint is based on the <em>individual item</em> instead of the <em>width allotted to the parent</em></li> <li>items will start new rows independent of each other vs. all at once without needing to define unique breakpoints</li> <li>we can use <code>ch</code> because there's no <code>calc</code> involved</li> </ul> <p>One potential downside is the need for nested elements to resolve conflicts between setting of the width custom variable (<code>--pancake-min</code>), as shown on the demo when trying to set a new breakpoint for the card paragraph content.</p> <p>Here's the essential CSS:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--pancake-min</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.flex-pancake </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem -0.5rem<span class="token punctuation">;</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 1 1 <span class="token function">var</span><span class="token punctuation">(</span>--pancake-min<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>This looks similar to how we setup the Flexbox Albatross solution up until we get to the <code>flex</code> rule for the children.</p> <p>The shorthand including the computed variable breaks out to:</p> <pre class="language-css"><code class="language-css"><span class="token property">flex-grow</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-shrink</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span></code></pre> <p>We're allowing items to both grow and shrink until the area is too narrow for <em>all</em> items to fit based on the <code>flex-basis</code>, at which point the items will begin to drop to new rows. This makes the behavior similar to the grid solution <em>except</em> the dropped item will expand to fill the available area.</p> <p><em>This solution has become my preference in most cases</em>.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d1eyf7oen2yy6q17c8tm.gif" alt="demo of the deconstructed pancake technique" /></p> <div class="heading-wrapper h3"> <h3 id="holy-grail-variable-width-breakpoint-columns">Holy Grail: Variable Width Breakpoint Columns</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#holy-grail-variable-width-breakpoint-columns" aria-labelledby="holy-grail-variable-width-breakpoint-columns"><span hidden="">#</span></a></div> <p>Flexbox will allow us to create a method to designate that <em>some columns</em> should shrink to their &quot;auto&quot; width, while other columns have independent &quot;min-width&quot; behavior. This results in variable width columns that ultimately retain &quot;breakpoint&quot; behavior based on container width.</p> <p>Two keys that enable flexbox as the solution:</p> <ul> <li>&quot;column&quot; width is able to be independently set on flex children</li> <li>&quot;wrapping&quot; is inherently triggered when content exceeds the available horizontal width which is based on the container</li> </ul> <p>We will create a utility class to assign the &quot;auto width&quot; behavior:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>.flex--auto </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 0 1 auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>This shorthand computes to:</p> <pre class="language-css"><code class="language-css"><span class="token property">flex-grow</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">flex-shrink</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></code></pre> <p>Resulting in the behavior of only growing to what grid would consider <code>max-content</code> and being allowed to shrink indefinitely.</p> <p>Coupled with other flex children that use the flexbox behavior, the <code>.flex--auto</code> items will <em>appear</em> to break into row behavior once their siblings actually break from hitting their allowed minimum width.</p> <p>To illustrate this, we can setup the following inside one of our existing &quot;cards&quot;. Notice the update on the <code>--flex-min</code> value. That can be adjusted to taste depending on content presented, and it will only apply to flex children without <code>.flex--auto</code>. We could move it to be applied on the <code>span</code> within the <code>li</code> to adjust more exclusively if needed.</p> <pre class="language-html"><code class="language-html"><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>list-unstyled<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 8rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Ice Cream<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Butter Pecan<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Musical Artist<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Justin Timberlake<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Painter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Vincent Van Gogh<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <p><em>Note this also works with the Deconstructed Pancake method.</em></p> <p>And here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o2hhpaqb9jxhsyrfxbhb.gif" alt="demo of holy grail behavior" /></p> <p>You may feel this example is a little jarring, but it illustrates how each list item has independent wrapping behavior once the non-auto item hits the <code>8rem</code> minimum width due to the container width shrinking.</p> <p>More practically, this is also applied on the icon + title lockup in the demo. The demo also shows the same list using the albatross behavior to provide a way to compare the methods.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <blockquote> <p>I encourage you to open this up in CodePen to be able to manipulate the viewport size.</p> </blockquote> <p>The top row (green outline) uses the grid solution, the middle row (red outline) uses the Flexbox Albatross solution, and the bottom row (purple outline) uses the Deconstructed Pancake solution. The second card list for each flexbox solution demonstrates the &quot;holy grail&quot; solution per-list item.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="PoZqEVG" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="when-to-use-each-method">When to Use Each Method</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#when-to-use-each-method" aria-labelledby="when-to-use-each-method"><span hidden="">#</span></a></div> <p><strong>Choose <code>grid</code> if</strong>:</p> <ul> <li>Equal-width columns with more &quot;reader friendly&quot; minimum width settings are a priority (<code>ch</code> vs. <code>rem</code>)</li> <li>It's more desirable for columns to break a bit more independently when they arrive at the minimum acceptable width</li> <li>&quot;Orphan&quot; columns for odd numbers of columns are acceptable (seen on the demo at a mid-size viewport)</li> </ul> <p><strong>Choose <code>flex</code> if</strong>:</p> <ul> <li>You need variable width columns that still have &quot;breakpoint&quot; behavior based on container size</li> <li>Lack of full <code>gap</code> support is not an issue</li> </ul> <p><strong>Choose the Flexbox Albatross if</strong>:</p> <ul> <li>It's acceptable for columns to hit the breakpoint simultaneously</li> <li>Fallout of adjustment of that breakpoint is acceptable (possibility of extra columns breaking to rows as described)</li> <li>You want the <code>flex-basis</code> breakpoint to be the <em>parent's width</em></li> </ul> <p><strong>Choose the Deconstructed Pancake if</strong>:</p> <ul> <li>You want the <code>flex-basis</code> breakpoint based on the <em>item</em> width</li> <li>You want items to break to new rows on an individual basis</li> <li>You want to use CSS units like <code>ch</code> that aren't allowed in <code>calc</code></li> </ul> Generating `font-size` CSS Rules and Creating a Fluid Type Scale 2020-05-28T00:00:00Z https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/ <p>Let's take the mystery out of sizing type. Typography is both foundational to any stylesheet and the quickest way to elevate an otherwise minimal layout from drab to fab. If you're looking for type design theory or how to select a font, that's outside the scope of this article. The goal for today is to give you a foundation for developing essential type styles in CSS, and terms to use if you wish to explore any topics deeper.</p> <p>This episode covers:</p> <ul> <li>recommended units for <code>font-size</code></li> <li>generating ratio-based <code>font-size</code> values with Sass</li> <li>recommended properties to prevent overflow from long words/names/URLs</li> <li>defining viewport-aware fluid type scale rules with <code>clamp()</code></li> <li>additional recommendations for dealing with type</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="defining-type-scale">Defining &quot;Type Scale&quot;</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#defining-type-scale" aria-labelledby="defining-type-scale"><span hidden="">#</span></a></div> <p>The simplified definition: &quot;type scale&quot; for the web refers to properties such as <code>font-size</code>, <code>line-height</code>, and often <code>margin</code>, that work together to create <em>vertical rhythm</em> in your design. These can be arbitrarily selected (&quot;it just looks good&quot;), or be based on the idea of a &quot;modular scale&quot; that employs ratio-based sizing.</p> <p>At a minimum, it involves setting a base <code>font-size</code> and <code>line-height</code> on <code>body</code> which elements such as paragraphs and list items will inherit by default.</p> <p>Then, set <code>font-size</code> and <code>line-height</code> on heading elements, particularly levels <code>h1</code>-<code>h4</code> for general use.</p> <h4>What about <code>h5</code> and <code>h6</code>?</h4> <p>Certain scenarios might make it beneficial to care about levels 5 and 6 as well, but <strong>it's important to not use a heading tag when it's really a visual style that's desired</strong>. Overuse of heading tags can cause noise or generally impart poor information hierarchy for assistive tech when another element would be better suited for the context.</p> <div class="heading-wrapper h2"> <h2 id="selecting-a-unit-for-font-size">Selecting a Unit for <code>font-size</code></h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#selecting-a-unit-for-font-size" aria-labelledby="selecting-a-unit-for-font-size"><span hidden="">#</span></a></div> <p><code>px</code>, <code>%</code>, <code>rem</code>, and <code>em</code>, oh my!</p> <p>The first upgrade is to forget about <code>px</code> when defining typography. It is not ideal due to failure to scale in proportion to the user's default <code>font-size</code> that they may have set as a browser preference or by using zoom.</p> <p>Instead, it's recommended that your primary type scale values are set with <code>rem</code>.</p> <p>Unless a user changes it, or you define it differently with <code>font-size</code> on an <code>html</code> rule, the default <code>rem</code> value is 16px with the advantage of responding to changes in operating system zoom level.</p> <p>In addition, the value of <code>rem</code> will not change no matter how deeply it is nested, which is largely what makes it the preferred value for typography sizing.</p> <p>A few years ago, you might have started to switch your <code>font-size</code> values to <code>em</code>. Let's learn the difference.</p> <p><code>em</code> will stay proportionate to the element or nearest ancestor's <code>font-size</code> rule. One <code>em</code> is equal to the <code>font-size</code>, so by default, this is equal to <code>1rem</code>.</p> <p>Compared to <code>rem</code>, <code>em</code> can compound from parent to child, causing adverse results. Consider the following example of a list where the <code>font-size</code> is set in <code>em</code> and compounds for the nested lists:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNVeLdM" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>Learn more about units including <code>rem</code> and <code>em</code> in my <a href="https://dev.to/5t3ph/guide-to-css-units-for-relational-spacing-1mj5">Guide to CSS Units for Relative Spacing</a></p> </blockquote> <p><code>em</code> shines when the behavior of spacing relative to the element is the desired effect.</p> <p>One use case is for <a href="https://moderncss.dev/css-button-styling-guide/">buttons</a> when sizing an icon relative to the button text. Use of <code>em</code> will scale the icon proportionate to the button text without writing custom icon sizes if you have variants in the button size available.</p> <p>Percentages have nearly equivalent behavior to <code>em</code> but typically <code>em</code> is still preferred when relative sizing is needed.</p> <div class="heading-wrapper h3"> <h3 id="calculating-px-to-rem">Calculating <code>px</code> to <code>rem</code></h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#calculating-px-to-rem" aria-labelledby="calculating-px-to-rem"><span hidden="">#</span></a></div> <p>I've spent my career in marketing and working with design systems, so I can relate to those of you being given px-based design specs :)</p> <p>You can create calculations by assuming that <code>1rem</code> is <code>16px</code> - or use an <a href="https://borderleft.com/toolbox/rem/">online calculator</a> to do the work for you!</p> <div class="heading-wrapper h2"> <h2 id="baseline-type-styles">Baseline Type Styles</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#baseline-type-styles" aria-labelledby="baseline-type-styles"><span hidden="">#</span></a></div> <p>A solid starting point is to define:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">body </span><span class="token punctuation">{</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Older recommendations may say to use <code>100%</code>, and this article previously recommended <code>1rem</code>. However, the only element the <code>body</code> can inherit from is <code>html</code> which is where the <code>rem</code> unit takes its value and so defining it here is redundant.</p> <p>So, we'll only add one rule which is for accessibility. It is recommended to have a minimum of 1.5 <code>line-height</code> for legibility. This can be affected by various factors, particularly font in use, but is the recommended starting value.</p> <div class="heading-wrapper h3"> <h3 id="preventing-text-overflow">Preventing Text Overflow</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#preventing-text-overflow" aria-labelledby="preventing-text-overflow"><span hidden="">#</span></a></div> <p>We can add some future-proof properties to help prevent overflow layout issues due to long words, names, or URLs.</p> <p>This is optional, and you may prefer to scope these properties to component-based styles or create a utility class to more selectively apply this behavior.</p> <p>We'll scope these to headings as well as <code>p</code> and <code>li</code> for our baseline:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">p, li, h1, h2, h3, h4 </span><span class="token punctuation">{</span> <span class="token comment">// Help prevent overflow of long words/names/URLs</span> <span class="token property">overflow-wrap</span><span class="token punctuation">:</span> break-word<span class="token punctuation">;</span> <span class="token comment">// Optional, not supported for all languages</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As of testing for this episode, <code>overflow-wrap: break-word;</code> seemed sufficient, whereas looking back on articles over the past few years seem to recommend more properties for the same effect.</p> <p>The <code>hyphens</code> property is still <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#Browser_compatibility">lacking in support</a>, particularly when you may be dealing with multi-language content. However, it gracefully falls back to simply no hyphenation in which case <code>overflow-wrap</code> will still help. More testing may be required for certain types of content where long words are the norm, ex. scientific/medical content.</p> <p><a href="https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/">This CSS-Tricks article</a> covers additional properties in-depth if you do find these two properties aren't quite cutting it.</p> <div class="heading-wrapper h2"> <h2 id="ratio-based-type-scales">Ratio-based Type Scales</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#ratio-based-type-scales" aria-labelledby="ratio-based-type-scales"><span hidden="">#</span></a></div> <p>While I introduced this episode by saying we wouldn't cover type design theory, we will use the concept of a &quot;type scale&quot; to efficiently generate <code>font-size</code> values.</p> <p>Another term for ratio-based is &quot;modular&quot;, and here's a great article introducing the term by <a href="https://alistapart.com/article/more-meaningful-typography/">Tim Brown on A List Apart</a>.</p> <p>There are some named ratios available, and our Codepen example creates a Sass map of them for ease of reference:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$type-ratios</span></span><span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token string">"minorSecond"</span><span class="token punctuation">:</span> 1.067<span class="token punctuation">,</span> <span class="token string">"majorSecond"</span><span class="token punctuation">:</span> 1.125<span class="token punctuation">,</span> <span class="token string">"minorThird"</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">,</span> <span class="token string">"majorThird"</span><span class="token punctuation">:</span> 1.25<span class="token punctuation">,</span> <span class="token string">"perfectFourth"</span><span class="token punctuation">:</span> 1.333<span class="token punctuation">,</span> <span class="token string">"augmentedFourth"</span><span class="token punctuation">:</span> 1.414<span class="token punctuation">,</span> <span class="token string">"perfectFifth"</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">,</span> <span class="token string">"goldenRatio"</span><span class="token punctuation">:</span> 1.618<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><em>These ratios were procured from the really slick online calculator <a href="https://type-scale.com/">Type Scale</a></em></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="generating-font-size-using-a-selected-ratio">Generating <code>font-size</code> Using a Selected Ratio</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#generating-font-size-using-a-selected-ratio" aria-labelledby="generating-font-size-using-a-selected-ratio"><span hidden="">#</span></a></div> <p>Stick with me - I don't super enjoy math, either.</p> <p>The good news is we can use Sass to do the math and output styles dynamically in relation to any supplied ratio 🙌</p> <blockquote> <p>Unfamiliar with Sass? It's a preprocessor that gives your CSS superpowers - like variables, array maps, functions, and loops - that compile to regular CSS. <a href="https://sass-lang.com/guide">Learn more about Sass &gt;</a></p> </blockquote> <p>There are two variables we'll define to get started:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Recommended starting point</span> <span class="token property"><span class="token variable">$type-base-size</span></span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">// Select by key of map, or use a custom value</span> <span class="token property"><span class="token variable">$type-size-ratio</span></span><span class="token punctuation">:</span> <span class="token function">type-ratio</span><span class="token punctuation">(</span><span class="token string">"perfectFourth"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>$type-size-ratio</code> is selecting the <code>perfectFourth</code> ratio from the map previewed earlier, which equals <code>1.333</code>.</p> <p><em>The CodePen demo shows how the <code>type-ratio()</code> custom Sass function is created to retrieve the ratio value by key. For use in a single project, you can skip adding the map entirely and directly assign your chosen ratio decimal value to</em> <code>$type-size-ratio</code>.</p> <p>Next, we define the heading levels that we want to build up our type scale from. As discussed previously, we will focus on <code>h1</code>-<code>h4</code>.</p> <p>We create a variable to hold a list of these levels so that we can loop through them in the next step.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// List in descending order to prevent extra sort function</span> <span class="token property"><span class="token variable">$type-levels</span></span><span class="token punctuation">:</span> 4<span class="token punctuation">,</span> 3<span class="token punctuation">,</span> 2<span class="token punctuation">,</span> 1<span class="token punctuation">;</span></code></pre> <p>These are listed starting with <code>4</code> because <code>h4</code> should be the smallest - and closest to the body size - of the heading levels.</p> <p>Time to begin our loop and add the math.</p> <blockquote> <p>Before I lose you, I want to let you know that in an updated tutorial we <a href="https://moderncss.dev/container-query-units-and-fluid-typography/">use custom properties and no Sass</a> to create this same result. Plus, we upgrade to using container query units for fluid typography that is independent of the viewport!</p> </blockquote> <p>First, we create a variable that we will update on each iteration of the loop. To start with, it uses the value of <code>$type-base-size</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$level-size</span></span><span class="token punctuation">:</span> <span class="token variable">$type-base-size</span><span class="token punctuation">;</span></code></pre> <p>If you are familiar with Javascript, we are creating this as essentially a <code>let</code> scoped variable.</p> <p>Next, we open our <code>@each</code> loop and iterate through each of the <code>$type-levels</code>. We compute the <code>font-size</code> value / re-assign the <code>$level-size</code> variable. This compounds <code>$level-size</code> so that is scales up with each heading level and is then multiplied by the ratio for the final <code>font-size</code> value.</p> <pre class="language-scss"><code class="language-scss"><span class="token keyword">@each</span> <span class="token selector"><span class="token variable">$level</span> in <span class="token variable">$type-levels</span> </span><span class="token punctuation">{</span> <span class="token property"><span class="token variable">$level-size</span></span><span class="token punctuation">:</span> <span class="token variable">$level-size</span> <span class="token operator">*</span> <span class="token variable">$type-size-ratio</span><span class="token punctuation">;</span> <span class="token comment">// Output heading styles</span> <span class="token comment">// Assign to element and create utility class</span> <span class="token selector">h<span class="token variable">#{$level}</span> </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token variable">$level-size</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Given the <code>perfectFourth</code> ratio, this results in the following <code>font-size</code> values:</p> <pre class="language-bash"><code class="language-bash">h4: <span class="token number">1</span>.333rem h3: <span class="token number">1</span>.776889rem h2: <span class="token number">2</span>.368593037rem h1: <span class="token number">3</span>.1573345183rem</code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/j42cx5vf0fm9ucp0nqnc.png" alt="preview of 'perfectFourth' type sizes" /></p> <p><em>Example phrase shamelessly borrowed from Google fonts 🙃</em></p> <p><em>h/t to this David Greenwald article on <a href="https://www.rawkblog.com/2018/05/modular-scale-typography-with-css-variables-and-sass/">Modular Scale Typography</a> which helped connect the dots for me on getting the math correct for ratio-based sizing. He also shows how to accomplish this with CSS <code>var()</code> and <code>calc()</code></em></p> <div class="heading-wrapper h3"> <h3 id="line-height-and-vertical-spacing"><code>line-height</code> and Vertical Spacing</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#line-height-and-vertical-spacing" aria-labelledby="line-height-and-vertical-spacing"><span hidden="">#</span></a></div> <p>At a minimum, it would be recommended to include a <code>line-height</code> update within this loop. The preview image already included this definition, as without it, large type generally doesn't fare well from inherits the <code>1.5</code> rule.</p> <p><a href="https://hugogiraudel.com/2020/05/18/using-calc-to-figure-out-optimal-line-height/">A recent article</a> by Jesús Ricarte is very timely from our use case, which proposes the following clever calculation:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>2px <span class="token operator">+</span> 2ex <span class="token operator">+</span> 2px<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>ex</code> unit is intended to be equivalent to the <code>x</code> height of a font. Jesús tested a few solutions and devised the <code>2px</code> buffers to further approach an appropriate <code>line-height</code> that is able to scale. It even scales with fluid - aka &quot;responsive&quot; type - which we will create next.</p> <p>As for vertical spacing, if you are using a CSS reset it may include clearing out all or one direction of margin on typography elements for you.</p> <p>Check via Inspector to see if your type is still inheriting margin styles from the browser. If so, revisit the rule where we handled overflow and add <code>margin-top: 0</code>.</p> <p>Then, in our heading loop, my recommendation is to add:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span></code></pre> <p>As we learned, <code>em</code> is relative to the <code>font-size</code>, so by using it as the unit on <code>margin-bottom</code> we will achieve space that is essentially 65% of the <code>font-size</code>. You can experiment with this number to your taste, or explore the vast sea of articles that go into heavier theory on vertical rhythm in type systems to find your preferred ideal.</p> <div class="heading-wrapper h2"> <h2 id="fluid-type-aka-responsive-typography">Fluid Type - aka Responsive Typography</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#fluid-type-aka-responsive-typography" aria-labelledby="fluid-type-aka-responsive-typography"><span hidden="">#</span></a></div> <p>If you choose a ratio that results in rather large <code>font-size</code> on the upper end, you are likely to experience overflow issues on small viewports despite our earlier attempt at prevention.</p> <p>This is one reason techniques for &quot;fluid type&quot; have come into existence.</p> <p>Fluid type means defining the <code>font-size</code> value in a way that responds to the viewport size, resulting in a &quot;fluid&quot; reduction of size, particularly for larger type.</p> <p>There is a singular modern CSS property that will handle this exceptionally well: <code>clamp</code>.</p> <p>The <code>clamp()</code> function takes three values. Using it, we can set a minimum allowed font size value, a scaling value, and a max allowed value. This effectively creates a range for each <code>font-size</code> to transition between, and it will work thanks to viewport units.</p> <blockquote> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp()">Learn more about <code>clamp()</code> on MDN</a>, and <a href="https://caniuse.com/css-math-functions">check browser support</a> (currently 90.84%)</p> </blockquote> <p>We'll leave our existing loop in place because we still want the computed ratio value. And, the <code>font-size</code> we've set will become the fallback for browsers that don't yet understand <code>clamp()</code>.</p> <p>But - we have to do more math 😊</p> <p>In order to correctly perform the math, we need to do a bit of a hack (thanks, Kitty at <a href="https://css-tricks.com/snippets/sass/strip-unit-function/">CSS-Tricks</a>!) to remove the unit from our <code>$level-size</code> value:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Remove unit for calculations</span> <span class="token property"><span class="token variable">$level-unitless</span></span><span class="token punctuation">:</span> <span class="token variable">$level-size</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token variable">$level-size</span> <span class="token operator">*</span> 0 <span class="token operator">+</span> 1<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Next, we need to compute the minimum size that's acceptable for the font to shrink to.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Set minimum size to a percentage less than $level-size</span> <span class="token comment">// Reduction is greater for large font sizes (> 4rem) to help</span> <span class="token comment">// prevent overflow due to font-size on mobile devices</span> <span class="token property"><span class="token variable">$fluid-reduction</span></span><span class="token punctuation">:</span> <span class="token function">if</span><span class="token punctuation">(</span><span class="token variable">$level-size</span> <span class="token operator">></span> 4<span class="token punctuation">,</span> 0.5<span class="token punctuation">,</span> 0.33<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> <span class="token variable">$level-unitless</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token variable">$fluid-reduction</span> <span class="token operator">*</span> <span class="token variable">$level-unitless</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>You can adjust the if/else values for the <code>$fluid-reduction</code> variable to your taste, but this defines that for <code>$level-size</code> greater than <code>4rem</code>, we'll allow a reduction of <code>0.5</code> (50%) - and smaller sizes are allowed a <code>0.33</code> (33%) reduction.</p> <p>In pseudo-math, here's what's happening for the <code>h4</code> using the <code>perfectFourth</code> ratio:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> 1.33rem <span class="token operator">-</span> <span class="token punctuation">(</span>33% of 1.33<span class="token punctuation">)</span> = 0.89311<span class="token punctuation">;</span></code></pre> <p>The result is a 33% allowed reduction from the base <code>$level-size</code> value.</p> <p>The pseudo-math actually exposes an issue: this means that the <code>h4</code> <em>could</em> shrink below the <code>$type-base-size</code> (reminder: this is the base <code>body</code> font size).</p> <p>Let's add one more guardrail to prevent this issue. We'll double-check the result of <code>$fluid-min</code> and if it's going to be below <code>1</code> - the unitless form of <code>1rem</code> - we just set it to <code>1</code> (adjust this value if you have a different <code>$type-base-size</code>):</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Prevent dropping lower than 1rem (body font-size)</span> <span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> <span class="token function">if</span><span class="token punctuation">(</span><span class="token variable">$fluid-min</span> <span class="token operator">></span> 1<span class="token punctuation">,</span> <span class="token variable">$fluid-min</span><span class="token punctuation">,</span> 1<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>We're missing one value which I have taken to calling the &quot;scaler&quot; - as in, the value that causes the fluid scaling to occur. It needs to be a value that by it's nature will change in order to trigger the transition between our min and max values.</p> <p>So, we'll be incorporating viewport units - specifically <code>vw</code>, or &quot;viewport width&quot;. When the viewport width changes, then this value will also update it's computed value. When it approaches our minimum value, it won't shrink further, and the true in the opposite direction for our max value. This creates the &quot;fluid&quot; type sizing effect.</p> <p>In order to retain accessible sizing via zooming, we'll also add <code>1rem</code> alongside our <code>vw</code> value. This helps alleviate (but not entirely rule out) side effects of using viewport units only. This is because as was mentioned earlier, the <code>rem</code> unit will scale with the user's zoom level as set via either their operating system <em>or</em> with in-browser zoom. To meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html">WCAG Success Criterion 1.4.4: Resize text</a>, a user must be able to increase font-size by up to 200%.</p> <p>Let's create our scaler value:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$fluid-scaler</span></span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$level-unitless</span> <span class="token operator">-</span> <span class="token variable">$fluid-min</span><span class="token punctuation">)</span> <span class="token operator">+</span> 4vw<span class="token punctuation">;</span></code></pre> <p>The logic applied here is to get the difference between the upper and lower limit, and add that value to a viewport unit of choice, in this case <code>4vw</code>. A value of 4 or 5 seems to be common in fluid typography recommendations, and testing against the <code>$type-ratios</code> seemed to surface <code>4vw</code> as keeping the most definition between heading levels throughout scaling. Please get in touch if you have a more formulaic way to arrive at the viewport value!</p> <p>Altogether, our fluid type <code>font-size</code> rule becomes:</p> <!-- prettier-ignore --> <pre class="language-scss"><code class="language-scss"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token variable">#{$fluid-min}</span>rem<span class="token punctuation">,</span> <span class="token variable">#{$fluid-scaler}</span> <span class="token operator">+</span> 1rem<span class="token punctuation">,</span> <span class="token variable">#{$level-size}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h2"> <h2 id="in-closing">In Closing...</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#in-closing" aria-labelledby="in-closing"><span hidden="">#</span></a></div> <p>If you really read this whole episode, thank you <em>so much</em> for sticking with it. Typography has so many angles and the &quot;right way&quot; is very project-dependent. It may be the set of properties with the most stakeholders and the most impact on any given layout.</p> <blockquote> <p>Consider reviewing the upgraded solution that drops Sass to use custom properties, and uses <a href="https://moderncss.dev/container-query-units-and-fluid-typography/">container query units for fluid typography</a> that flows easily into multiple layout locations.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>The demo includes all things discussed, and an extra bit of functionality which is that a map is created under the variable <code>$type-styles</code> to hold each generated value with <code>h[x]</code> as the key.</p> <p>Following the loop is the creation of the <code>type-style()</code> function that can retrieve values from the map based on a key such as <code>h3</code>. This can be useful for things like design systems where you may want to reference the <code>h3</code> font-size on the component level for visual consistency when perhaps a heading is semantically incorrect.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNOgeYv" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Resource: The Complete Guide to Centering in CSS 2020-05-17T00:00:00Z https://moderncss.dev/resource-the-complete-guide-to-centering-in-css/ <p>How could I write a series called &quot;Modern CSS Solutions <em>to old CSS problems</em>&quot; without covering the classic question:</p> <blockquote> <p>&quot;How do I center a div?&quot;</p> </blockquote> <p>Well, this new resource has you covered! We'll look at 3 categories:</p> <ul> <li>Vertically and Horizontally (XY)</li> <li>Vertical (Y)</li> <li>Horizontal (X)</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Each category shows solutions that explore using grid, flexbox, and block element layout.</p> <p>Check out <a href="https://moderncss.dev/complete-guide-to-centering-in-css/">the full guide</a> or jump to one of the following sections:</p> <ol> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#vertically-and-horizontally-xy">Vertically and Horizontally (XY)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-grid-solution">XY Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-flexbox-solution">XY Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-alternative-flexbox-solution">XY Alternative Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-centering-for-block-elements">XY Centering for Block Elements</a></li> </ul> </li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#vertical-centering-y">Vertical Centering (Y)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-grid-solution">Y Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-flexbox-solution">Y Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-centering-for-block-elements">Y Centering for Block Elements</a></li> </ul> </li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#horizontal-centering-x">Horizontal Centering (X)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-grid-solution">X Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-flexbox-solution">X Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-centering-for-block-elements">X Centering for Block Elements</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-centering-for-dynamically-positioned-elements">X Centering for Dynamically Positioned Elements</a></li> </ul> </li> </ol> Icon Button CSS Styling Guide 2020-05-13T00:00:00Z https://moderncss.dev/icon-button-css-styling-guide/ <p>This guide will build on the previous episode <a href="https://moderncss.dev/css-button-styling-guide/">&quot;CSS Button Styling Guide&quot;</a> to explore the use case of icon buttons. We'll cover icon + text as well as icon-only buttons.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Note</strong>: With SVG now having excellent support, the preferred practice is to use it for icon systems vs. icon fonts. We will not dive into SVGs specifically, but we will assume SVG icons are in use.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="icon-text-button">Icon + Text Button</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-text-button" aria-labelledby="icon-text-button"><span hidden="">#</span></a></div> <p>First, let's do the easier extend from our current buttons, and drop an svg icon next to the text:</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>button__icon<span class="token punctuation">"</span></span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> Button Link <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>There are 3 key features about the SVG for the icon + text use case:</p> <ol> <li>Use of a new <code>button__icon</code> class</li> <li>The <code>viewBox</code> value is tight to the icon boundaries, ideally a square for best results across the icon set even if the values have variance (ex. <code>24</code> vs. <code>32</code>)</li> <li>For accessibility, we apply:</li> </ol> <ul> <li><code>aria-hidden=&quot;true&quot;</code> - allows assistive tech to skip the SVG since it's decorative and not providing any semantic value not already provided by the visible button text</li> <li><code>focusable=&quot;false&quot;</code> - prevents a &quot;double focus&quot; event in some version of IE</li> </ul> <blockquote> <p><strong>For more on accessibility of icon buttons</strong>: Read <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">this excellent article</a> by Sara Soueidan who is an expert on both accessibility and SVGs</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="icon-styling-for-icon-text">Icon Styling for Icon + Text</h3> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-styling-for-icon-text" aria-labelledby="icon-styling-for-icon-text"><span hidden="">#</span></a></div> <p>Due to <code>display: inline-flex</code> applied on the base <code>.button</code>, and no <code>width</code> attribute on the SVG, by default the icon is not yet visible.</p> <p>So let's first add dimensions to our new <code>.button__icon</code> class, using the <code>em</code> unit to keep it relative to the <code>font-size</code> in use:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// You may wish to have your icons appear larger</span> <span class="token comment">// or smaller in relation to the text</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/00g7uw9dfcb80pq2hikz.png" alt="button icon with dimensions" /></p> <p>According to <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill#path">the spec default</a>, SVG parts including <code>path</code> have a <code>fill</code> of black. To adjust this, we will use the special keyword <code>currentColor</code> which will extend the button's applied text <code>color</code> to the SVG:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">fill</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0rs7lk1bmq6hqkcggekq.png" alt="button icon with currentColor as fill" /></p> <p>The last thing to adjust is to add a bit of spacing between the icon and button text, which we will again apply using the <code>em</code> unit:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/niqz77ol4aaskwjic6dw.png" alt="button icon with spacing applied" /></p> <p>We need to add one utility class to allow the icon to be placed after the text, or at the &quot;end&quot; of the button (for right-to-left languages). We zero out the existing margin, and flip it to the left:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>--end </span><span class="token punctuation">{</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Button Link <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>button__icon button__icon--end<span class="token punctuation">"</span></span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/xj30apl4rbcnzs1vjs8r.png" alt="icon placed at the end of the button" /></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="icon-only-buttons">Icon-Only Buttons</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-only-buttons" aria-labelledby="icon-only-buttons"><span hidden="">#</span></a></div> <p>We're going to make the assumption that we want both regular buttons (with or without icons) in addition to icon-only buttons. This is important because we will reuse the <code>.button</code> class in addition to a new class so that we don't have to redefine the resets and base visual styles. The overrides are minimal.</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button icon-button<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>Icon-only Button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<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>icon-button__icon<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>Changes from the icon + text button:</p> <ol> <li>Addition of the <code>icon-button</code> class to the <code>a</code></li> <li>Addition of <code>aria-label=&quot;Icon-only Button&quot;</code> to provide an accessible text alternative since we have removed the visual text</li> <li>Swap of the class on the SVG to <code>icon-button__icon</code></li> </ol> <blockquote> <p><strong>Important</strong>: the value of the <code>aria-label</code> should describe <em>what the button does</em> <strong>not</strong> <em>what the icon is</em>. For further reading and other ways to provide a text alternative, see <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">Sara Soueidan's article</a></p> </blockquote> <p>Here's what we get before adjustments - an empty-looking button because we're back to the no-width problem:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/09pnf9xm2pectdy9ug6j.png" alt="pre-styled icon button" /></p> <p>First, let's create our new class. Due to <a href="https://dev.to/5t3ph/intro-to-the-css-cascade-the-c-in-css-1kh0">the &quot;C&quot; in CSS</a>, this rule needs to be placed after the <code>.button</code> rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.icon-button </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 2.5rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 2.5rem<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.35em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>__icon </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">fill</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We define a new <code>width</code> and <code>height</code> which is completely adjustable based on your design requirements, but it should equate to a square. This allows creation of a &quot;circle&quot; appearance when <code>border-radius: 50%</code> is applied.</p> <p>Then, we add a touch of padding (again to your tastes/design requirements) to add some breathing room between the SVG icon and the button boundary.</p> <p>Next, we define our <code>icon-button__icon</code> class. The difference here is that we want the <code>width</code> and <code>height</code> to match that of the container, so we set this to <code>100%</code>. This allows extending to multiple size icon-only buttons by only redefining the <code>font-size</code> property on the <code>.icon-button</code> class.</p> <p>Here's the progress:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/tdnn9ug4rcpmn45czqb1.png" alt="icon-only button styles" /></p> <p>It's not quite what we want, but we can fix it by adjusting the following properties within the <code>.button</code> class. We'll use the <code>:not()</code> selector to exclude the properties meant only for regular buttons:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token comment">// Find these styles and update, not duplicate:</span> &amp;<span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>.icon-button<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 44px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.75em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Now we have what we're after:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/aqknn8adugm9vn63c091.png" alt="completed icon-only button" /></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Includes use of the <code>.button--small</code> class created in the previous episode, as well as a &quot;real button&quot; to validate that styles work just as well for both elements:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="ExVpVJa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p><strong><a href="https://buttonbuddy.dev/">Try out ButtonBuddy to create accessible button colors</a></strong>. This web app I created will help get all the vectors of contrast right across your button color palette.</p> </blockquote> CSS Button Styling Guide 2020-05-07T00:00:00Z https://moderncss.dev/css-button-styling-guide/ <p>This guide will explore the ins and outs of styling an accessible, extensible button appearance for both link and button elements.</p> <p>Topics covered include:</p> <ul> <li>reset styles for <code>a</code> and <code>button</code></li> <li>display, visual, size, and text styles</li> <li>accessible styling considerations</li> <li>extended styles for common scenarios</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Oh, the button (or is it a link?). I've battled the button since the days of hover delay from waiting for a second image to load, through image sprites, and then was immensely relieved when <code>border-radius</code>, <code>box-shadow</code> and gradients arrived on the scene.</p> <p>But... we took button styling too far, and somewhere along the way completely lost sight of what it really means to be a button, let alone an accessible button (or link).</p> <blockquote> <p><strong>STOP!</strong> Go read this excellent article: <a href="https://marcysutton.com/links-vs-buttons-in-modern-web-applications">Links vs. Buttons in Modern Web Applications</a> to understand when it's appropriate to use <code>a</code> versus <code>button</code></p> </blockquote> <p>We'll look at what properties are required to visually create a button appearance for both <code>a</code> and <code>button</code>, and additional details required to ensure they are created and used accessibly.</p> <div class="heading-wrapper h2"> <h2 id="reset-default-styles">Reset Default Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#reset-default-styles" aria-labelledby="reset-default-styles"><span hidden="">#</span></a></div> <p>Here's our baseline - native browser styles as rendered in Chrome, with the only changes so far being the link is inheriting the custom font set on the body, and I've bumped the <code>font-size</code> as well:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1b5duijnf8zdydz1ue1p.png" alt="default link and button styles" /></p> <p>The HTML if you're playing along at home is:</p> <pre class="language-html"><code class="language-html"><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>javascript:;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Button Link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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 punctuation">></span></span>Real Button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <p>I've used the <code>javascript:;</code> string for the <code>href</code> value so that we could test states without triggering navigation. Similarly, since this button is not for a form submit, it needs the explicit type of <code>button</code> to prevent triggering a get request and page reload.</p> <div class="heading-wrapper h3"> <h3 id="reset-styles">Reset Styles</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#reset-styles" aria-labelledby="reset-styles"><span hidden="">#</span></a></div> <blockquote> <p><strong>Note</strong>: Typically I apply the <em>Normalize</em> reset to CodePens, but for this lesson we are starting from scratch to learn what is required to reset for buttons and links. Use of <em>Normalize</em> or other popular resets do some of these things for you.</p> </blockquote> <p>First, we'll add the class of <code>button</code> to both the link and the button just to emphasize where styles are being applied for this lesson.</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Button Link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">class</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 punctuation">></span></span>Real Button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <h4><code>box-sizing</code></h4> <p>Ensure your styles include the following reset - if you don't want it globally (you should) you can scope it to our button class.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">* </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In a nutshell, this rule prevent things like borders and padding from expanding the expected element size (ex. a 25% width remains 25%, not 25% + border width + padding).</p> <h4><code>a</code></h4> <p>For the link, we only have one reset to do:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button </span><span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This simply removes the underline.</p> <h4><code>button</code></h4> <p>Next, we have a few more rules required to reset the button:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">button.button </span><span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token operator">and</span> <span class="token punctuation">(</span><span class="token property">-ms-high-contrast</span><span class="token punctuation">:</span> active<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid currentcolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>There are some differences in the <code>display</code> value as well between browsers, but we're going to change it to a unique option shortly.</p> <p>With these reset styles, we now have this appearance:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/44rie4nuqfk5jwpkk6ff.png" alt="link and button with reset styles" /></p> <p><em>Thanks to <a href="https://twitter.com/overflowhidden/status/1260837671762571265">@overflowhidden</a> for providing a solution to ensure a perceivable button border for users with Windows High Contrast mode enabled</em>.</p> <div class="heading-wrapper h2"> <h2 id="display-styles">Display Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#display-styles" aria-labelledby="display-styles"><span hidden="">#</span></a></div> <p>What I have found to work best across many scenarios is <code>display: inline-flex</code> which gives us the content alignment power of flexbox but sits in the DOM within <code>inline-block</code> behavior.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-flex<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Flex alignment comes in handy should you add icons in the future, or impose width restrictions.</p> <div class="heading-wrapper h2"> <h2 id="visual-styles">Visual Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#visual-styles" aria-labelledby="visual-styles"><span hidden="">#</span></a></div> <p>Next we'll apply some standard visual styles which you can certainly adjust to your taste. This is the most flexible group of styles and you can leave out <code>box-shadow</code> and/or <code>border-radius</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$btnColor</span></span><span class="token punctuation">:</span> #3e68ff<span class="token punctuation">;</span> <span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token variable">$btnColor</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 3px 5px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.18<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Now our link and button are starting to look more alike:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ctowxoi9kaub1bm96bz8.png" alt="link and button with visual styles" /></p> <div class="heading-wrapper h3"> <h3 id="button-contrast">Button Contrast</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#button-contrast" aria-labelledby="button-contrast"><span hidden="">#</span></a></div> <p>There are two levels of contrast involved when creating initial button styles:</p> <ol> <li>At least 3:1 between the button background color, and the background it is displayed against</li> <li>At least 4.5:1 (for text less than 18.66px bold or 24px) or 3:1 (for text greater than those measures) between the button text and the button background</li> </ol> <p>Here's an infographic I created to demonstrate how the button colors relate to their contrast relationships:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/58an5ksgtys6vhhq1tb4.png" alt="An infographic showing a &quot;default&quot; button that is a midrange shade of purple with white letters next to it's &quot;focus&quot; state which is a darker purple. Icons and labels show that the contrast of the default purple to the page background (a light yellow) is 4.17, and contrast of the default purple to the white button text is 4.5. For the focus button, there is a 3.02 contrast between the default purple background and the focus purple background, and 13.62 between focus purple and the white button text, and 12.57 between the focus purple and the page background light yellow." /></p> <p>Assuming a white page background, our button color choice passes with 4.54:1.</p> <blockquote> <p><strong><a href="https://buttonbuddy.dev/">Try out ButtonBuddy to create accessible button colors</a></strong>. This web app I created will help get all the vectors of contrast right across your button color palette.</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="size">Size</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#size" aria-labelledby="size"><span hidden="">#</span></a></div> <p>We intentionally left out one property under the &quot;Visual&quot; categorization that you might have missed upon seeing the progress screenshot: <code>padding</code>.</p> <p>Since <code>padding</code> is part of the <code>box-model</code>, we left it for the size section.</p> <p>Let's apply the size values and then discuss:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.75em<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 44px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We apply <code>padding</code> using <code>em</code> units, which is a preference that allows the padding to proportionally resize with the applied <code>font-size</code>.</p> <p>Next, we set a <code>min-width</code> using the <code>ch</code> unit, which is roughly equal to the width of the <code>0</code> character of the applied font and <code>font-size</code>. This recommendation is a visual rhythm guardrail. Consider the scenario you have two side-by-side buttons with one short and one longer label, ex. &quot;Share&quot; and &quot;Learn More&quot;. Without <code>min-width</code>, the &quot;Share&quot; button would be abruptly shorter than &quot;Learn More&quot;.</p> <p>The <code>min-height</code> is based on ensuring the button is a large enough target on touch devices to meet the WCAG 2.1 success criteria for <a href="https://www.w3.org/WAI/WCAG21/Understanding/target-size.html">2.5.5 - Target Size</a>.</p> <p>The styles are starting to come together, but we're not done yet:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/uylx2jbc92mr1bbdksoo.png" alt="link and button with size styles" /></p> <div class="heading-wrapper h2"> <h2 id="text-styles">Text Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#text-styles" aria-labelledby="text-styles"><span hidden="">#</span></a></div> <p>Based on the last progress screenshot, you might be tempted to skip text styles.</p> <p>But look what happens when we reduce the viewport size and trigger responsive behavior:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/s6rmhllldvvrw5wsgzhy.png" alt="link and button within reduced viewport" /></p> <p>As you can see, we have different alignment and the <code>line-height</code> could be adjusted as well.</p> <p>I intentionally skipped fixing text alignment in the reset styles, so we'll now make sure it's centered for both. Then we can also reduce the line-height - this may need adjusted depending on the font in use.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Alright, looking great!</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4ub4z1jxoxk3cp8gzg4a.png" alt="link and button with text styles" /></p> <div class="heading-wrapper h2"> <h2 id="state-styles">State Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#state-styles" aria-labelledby="state-styles"><span hidden="">#</span></a></div> <p>Right now, the only visual feedback a user receives when attempting to interact with the buttons is the cursor changing to the &quot;pointer&quot; variation.</p> <p>There are three states we need to ensure are present.</p> <div class="heading-wrapper h3"> <h3 id="hover"><code>:hover</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#hover" aria-labelledby="hover"><span hidden="">#</span></a></div> <p>The one that usually gets the most attention is <code>hover</code>, so we'll start there.</p> <p>A typical update on hover is changing the background color. Since we were fairly close to 4.5, we will want to darken the color.</p> <p>We can take advantage of Sass to compute this color for us using the <code>$btnColor</code> variable we defined in the &quot;Visual&quot; section:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>:hover </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -20%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>The effect is a little jarring, but we have another modern CSS tool to soften this, aptly named <code>transition</code>. The <code>transition</code> property will need to be added outside of the <code>hover</code> rule so that it applies both on &quot;over&quot; and &quot;out&quot;.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transition</span><span class="token punctuation">:</span> 220ms all ease-in-out<span class="token punctuation">;</span> <span class="token comment">// ...&amp;:hover</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0416wpo396xq4tbc5ln2.gif" alt="demo of hover transition" /></p> <div class="heading-wrapper h3"> <h3 id="focus"><code>:focus</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#focus" aria-labelledby="focus"><span hidden="">#</span></a></div> <p>For keyboard users, we need to ensure that the <code>focus</code> state is clearly distinguishable.</p> <p>By default, the browsers apply a sort of &quot;halo&quot; effect to elements that gain focus. A bad practice is simply removing the <code>outline</code> property which renders that effect and failing to replace it.</p> <p>We will replace the outline with a custom focus state that uses <code>box-shadow</code>. Like <code>outline</code>, <code>box-shadow</code> will not change the overall element size so it will not cause layout shifts. And, since we already applied a <code>transition</code>, the <code>box-shadow</code> will inherit that for use as well for an extra attention-getting effect.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token comment">// ...&amp;:hover</span> <span class="token selector"><span class="token parent important">&amp;</span>:focus </span><span class="token punctuation">{</span> <span class="token property">outline-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">outline-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 4px <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -40%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Once again, we have used the <code>scale-color</code> function, this time to go even a bit darker than the <code>hover</code> color. This is because a button can be in both the <code>hover</code> and <code>focus</code> states at the same time.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/03cq806lheglhyn5xf6l.gif" alt="demo of link and button focus" /></p> <p><em>Thanks to <a href="https://twitter.com/overflowhidden/status/1260837671762571265">@overflowhidden</a> for providing a solution to ensure a perceivable <code>:focus</code> state for users with Windows High Contrast mode enabled</em>.</p> <div class="heading-wrapper h3"> <h3 id="active"><code>:active</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#active" aria-labelledby="active"><span hidden="">#</span></a></div> <p>Lastly, particularly for the &quot;real button&quot;, it is best to define an <code>:active</code> state style.</p> <p>For links this appears for a brief moment during the &quot;down&quot; of a click/tap.</p> <p>For buttons, this can be shown for a longer duration given that a button can be triggered with the space key which can be held down indefinitely.</p> <p>We will append <code>:active</code> to our existing <code>:hover</code> style:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>:hover, <span class="token parent important">&amp;</span>:active </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -20%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="style-variations">Style Variations</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#style-variations" aria-labelledby="style-variations"><span hidden="">#</span></a></div> <p>The topic of outlined (&quot;ghost&quot;) buttons is a topic for a different day, but there are two variations that we'll quickly add.</p> <div class="heading-wrapper h3"> <h3 id="small-buttons">Small Buttons</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#small-buttons" aria-labelledby="small-buttons"><span hidden="">#</span></a></div> <p>Using BEM format, we'll create the <code>button--small</code> class to simply reduce font size. Since we set padding to <code>em</code>, that will proportionately resize. And our <code>min-height</code> will ensure the button remains a large enough touch target.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>--small </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="block-buttons">Block Buttons</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#block-buttons" aria-labelledby="block-buttons"><span hidden="">#</span></a></div> <p>There may be times you do want <code>block</code> behavior instead of inline, so we'll add <code>width: 100%</code> to allow for that option instead of changing the <code>display</code> prop since we still want flex alignment on the button contents:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>--block </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="gotcha-child-of-flex-columns">Gotcha: Child of Flex Columns</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#gotcha-child-of-flex-columns" aria-labelledby="gotcha-child-of-flex-columns"><span hidden="">#</span></a></div> <p>Given the scenario the button is a child of a flex column, you may be caught off guard when the button expands to full-width even without the <code>button--block</code> class.</p> <p>To future-proof against this scenario, you can add <code>align-self: start</code> to the base button styles, or create utility styles for each of the flex/grid alignment property values: <code>start</code>, <code>center</code>, and <code>end</code>.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNOpgPa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Solutions to Replace the 12-Column Grid 2020-05-03T00:00:00Z https://moderncss.dev/solutions-to-replace-the-12-column-grid/ <p>Let's create simplified responsive grid systems using both CSS grid and flexbox and ditch the bulk of 12-column grid systems from heavy frameworks.</p> <p>If you haven't really looked into grid, or rely on frameworks to think about flexbox for you, this will help you level up your understanding 🚀</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Looking across the web, you will often see content laid out in a few select flavors:</p> <ul> <li>fullwidth of its container</li> <li>two equal-width columns</li> <li>three equal-width columns</li> <li>four equal-width columns</li> </ul> <p>Usually, this is accomplished by a considerable amount of utility classes setting widths across breakpoints.</p> <p>Between CSS grid and flexbox, and with the aforementioned layouts in mind, we can greatly reduce the setup of responsive grid columns.</p> <p>For both solutions, we will create just two classes and be able to handle from 1-4 columns of content that responsively resizes equally 🙌</p> <blockquote> <p><strong>Note</strong>: These solutions as-is work best for defining primary page layout containers, but we'll end with some suggestions on filling the gap for other layout alignment needs.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="the-grid-solution">The Grid Solution</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#the-grid-solution" aria-labelledby="the-grid-solution"><span hidden="">#</span></a></div> <p>Grid excels at grids, as the name would imply. Here, the terms &quot;column&quot; and &quot;row&quot; are inherent to the way you work with CSS grid, which can make defining your solution more clear.</p> <p>In particular are the following useful features:</p> <ul> <li><code>gap</code> - defines equal space between grid items, whether columns or rows</li> <li><code>repeat()</code> - quickly define rules for every row or column, or a set number of rows or columns</li> <li><code>fr</code> unit - the available &quot;fraction&quot; of space that is left to distribute to that column or row</li> <li><code>minmax()</code> - define a minimum and maximum accepted column width or row height</li> </ul> <div class="heading-wrapper h3"> <h3 id="grid-wrap"><code>.grid-wrap</code></h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid-wrap" aria-labelledby="grid-wrap"><span hidden="">#</span></a></div> <p>First, we create a wrapping class. This is only to apply the equivalent of our <code>gap</code> value as padding and is totally optional. You may want this because the <code>gap</code> property does not apply the gap spacing to the outside of the grid. Perhaps padding is already applied to your containing element which may be the <code>body</code>, or you may actually want your grid columns to touch edge-to-edge of the viewport.</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$gridGap</span></span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector">.grid-wrap </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="grid"><code>.grid</code></h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid" aria-labelledby="grid"><span hidden="">#</span></a></div> <p>This is it - the one class that can quickly turn any element into a grid container where it's immediate children then become equal-width, responsive columns.</p> <p>Here's the full rule, and then we'll break it down:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$minColWidth</span></span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token variable">$minColWidth</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> + .grid </span><span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>First, we define a minimum width for our content columns. I recommend using <code>rem</code> for this value so that it is consistent throughout your experience. If we set it based on <code>em</code> it would be altered with any change in base element font-size. <a href="https://dev.to/5t3ph/guide-to-css-units-for-relational-spacing-1mj5">Learn more about working with units &gt;</a></p> <p>Then, the magic comes from how we define <code>grid-template-columns</code>.</p> <p>We use the <code>repeat</code> function to say that we want the same parameters applied across all columns that exist.</p> <p>Then, instead of an absolute number, we use the <code>auto-fit</code> value which is responsible for ensuring the columns stay equal-width by stretching columns to fill any available space.</p> <p>After that, we use <code>minmax()</code> to set the minimum allowed column width, and then use <code>1fr</code> as the max which ensures the content fills the column as much as room allows.</p> <p>Then we add our gap, and an optional rule to apply the same value between consecutive <code>.grid</code> containers.</p> <p>Here's the solution altogether:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvrZVx" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p><em>Note</em>: You could technically add many more than 4 columns within <code>.grid</code>, they will just become more narrow up until the minimum width even on larger viewports.</p> <blockquote> <p>Check out my <a href="https://egghead.io/lessons/css-create-a-basic-responsive-grid-system-with-css-grid">egghead video lesson</a> on how this technique comes together.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="drawbacks">Drawbacks</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#drawbacks" aria-labelledby="drawbacks"><span hidden="">#</span></a></div> <p>In the case of a 3-column + grid, while it does respond nicely, you will end up with an &quot;orphan&quot; column on some viewport widths.</p> <p>You can overcome this with media queries, but they will be brittle.</p> <p>If it is essential to the design to prevent orphan columns, you may want to opt for the flexbox solution instead.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-solution">Flexbox Solution</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flexbox-solution" aria-labelledby="flexbox-solution"><span hidden="">#</span></a></div> <p>Our flexbox solution will mimic grid in that the priority is equal-width columns.</p> <p>However, there is not yet a fully supported flexbox gap property (one is <a href="https://twitter.com/argyleink/status/1254794309263491072">on the way</a>!), so we have to do some trickery to accomplish the same effect.</p> <div class="heading-wrapper h2"> <h2 id="flex-grid-wrap"><code>.flex-grid-wrap</code></h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flex-grid-wrap" aria-labelledby="flex-grid-wrap"><span hidden="">#</span></a></div> <p>Same intention as the grid solution:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$gridGap</span></span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector">.flex-grid-wrap </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="flex-grid"><code>.flex-grid</code></h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flex-grid" aria-labelledby="flex-grid"><span hidden="">#</span></a></div> <p>Inherent flexbox behavior places items in a row where each item grows with content length and as it grows it bumps the next item over.</p> <p>So, we must add a bit of extra logic to create equal-width behavior.</p> <p>We define the rule with <code>display: flex</code>, and then we add a rule that directs immediate children to use <code>flex</code> behavior that evaluates to:</p> <ul> <li><code>flex-grow: 0</code> - prevents growing beyond an equitably shared amount of space</li> <li><code>flex-shrink: 1</code> - directs elements to &quot;shrink&quot; at the same rate</li> <li><code>flex-basis: 100%</code> - counteracts the <code>flex-grow</code> directive to still expand items to fill available space</li> </ul> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex-grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> > * </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 0 1 100%<span class="token punctuation">;</span> &amp;<span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span>first-child<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>And to make up for no gap rule, we define <code>margin-left</code> on all but the first item.</p> <div class="heading-wrapper h2"> <h2 id="handle-for-small-viewports">Handle for small viewports</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#handle-for-small-viewports" aria-labelledby="handle-for-small-viewports"><span hidden="">#</span></a></div> <p>Great start, but this will never break down for small viewports:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v9c82kc506hbcx09qcj9.png" alt="current flex column behavior on small viewport" /></p> <p>As noted at the start, since this grid solution is intended to be used for primary page layout containers, we will bring in media queries to insert a breakpoint by allowing for <code>flex-wrap: wrap</code>, and switching our margin &quot;gap hack&quot; to a top instead of left margin.</p> <p>To determine when to add wrapping, the baseline solution multiplies our minimal acceptable width by 3. The logic here is that once 3 columns individual widths are less than our acceptable minimum, we break and toss everything full-width instead. Depending on your acceptable minimum, you may alter this rule.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex-grid </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> @media <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$minColWidth</span> <span class="token operator">*</span> 3<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> > * </span><span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 2rem 0 0 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> @media <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$minColWidth</span> <span class="token operator">*</span> 3<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token selector"><span class="token parent important">&amp;</span> + .flex-grid </span><span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We also added a <code>min-width</code> query so that we have the top margin &quot;gap&quot; on larger viewports. If we had it on small as well, we would end up with double the margin between groups of content, which is possibly a desirable outcome.</p> <p>Here's the flexbox solution demo:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="eYpeNxd" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h3"> <h3 id="drawbacks-1">Drawbacks</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#drawbacks-1" aria-labelledby="drawbacks-1"><span hidden="">#</span></a></div> <p>Applying this grid to sub-containers within your page may cause undesirable breakpoint issues since it's a manual media query that is looking at the viewport width and not the container width.</p> <p><strong>Possible remedy</strong>: Instead of always applying the <code>max-width</code> query, you may apply that with a class. That would enable using this base grid idea for sub-containers with less undesirable results.</p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which Is Better?</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>The solutions proposed are very general but have wide application.</p> <p>The intent of each is to be applied to direct children of the <code>body</code>, or one layer deep such as to a <code>main</code> component that limits overall <code>max-width</code> of the content spread but still responds downward in sync with the viewport.</p> <div class="heading-wrapper h3"> <h3 id="choose-grid-if">Choose Grid if:</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#choose-grid-if" aria-labelledby="choose-grid-if"><span hidden="">#</span></a></div> <ul> <li>you want to take advantage of <code>auto-fit</code> + <code>minmax</code> behavior to automatically bump items to a new row once the minimum acceptable width is hit</li> <li>you plan to use in sub-containers since media queries are not required to apply breakpoints (you could extend the idea to apply to components like navbars or card action items by setting a smaller min-width)</li> <li>you'd like to <em>almost</em> achieve container queries since items respond according to their content length</li> </ul> <div class="heading-wrapper h3"> <h3 id="choose-flexbox-if">Choose Flexbox if:</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#choose-flexbox-if" aria-labelledby="choose-flexbox-if"><span hidden="">#</span></a></div> <ul> <li>the only place you need &quot;grid&quot; behavior is to layout primary page containers, such as to define rows of cards or create two-column text content</li> <li>you want to prevent &quot;orphan&quot; columns</li> </ul> <div class="heading-wrapper h2"> <h2 id="if-you-really-want-a-12-column-grid">If You <em>Really</em> Want A 12-Column Grid</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#if-you-really-want-a-12-column-grid" aria-labelledby="if-you-really-want-a-12-column-grid"><span hidden="">#</span></a></div> <p>Here it is - but you're responsible for placing items on it how you'd like which means more custom CSS rules :)</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>12<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Alternatively, create just a handful of targeted classes to more clearly define column expectations. Note that this type of usage means that columns will take up precisely the fraction of space that would equal 1/2, or 1/3, or 1/4. So if you have only one column in the <code>2cols</code> grid, it will still only span half the total width, not fill up available space.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>--2cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>2<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>--3cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>--4cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>4<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you're interested in a light-weight starting place for a basic HTML/Sass solution that includes minimal, general application layout containers and utilities, check out my <a href="https://5t3ph.github.io/html-sass-jumpstart/">jumpstart &gt;</a></p> </blockquote> CSS-Only Accessible Dropdown Navigation Menu 2020-04-23T00:00:00Z https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/ <p>This technique explores using:</p> <ul> <li>Animation with CSS <code>transition</code> and <code>transform</code></li> <li>Using the <code>:focus-within</code> pseudo-class</li> <li>CSS grid for positioning</li> <li>dynamic centering technique</li> <li>Accessibility considerations for dropdown menus</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>If you've ever pulled your hair out dealing with the concept of &quot;hover intent&quot;, then this upgrade is for you!</p> <p>Before we get too far, while our technique 100% uses only CSS, there is a need to add some Javascript for a more comprehensively accessible experience. There is also a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a> needed for a key feature to make this work - <code>:focus-within</code> - <a href="https://caniuse.com/#search=focus-within">for the most reliable support</a>. But we've still greatly improved from the days of needing one or more jQuery plugins to accomplish the visual effects.</p> <blockquote> <p><strong>Accessibility update - 08/18/20</strong>: A huge thanks to <a href="https://twitter.com/mfairchild365">Michael Fairchild</a> of Deque (and creator of the excellent resource <a href="https://a11ysupport.io/">a11ysupport.io</a>) for testing the original solution across various assistive technology. The CSS-only method needs some Javascript to fully meet WCAG 2.1. In particular, javascript needs to be used to offer a non-mouse/non-tab way to dismiss the menu (think escape key) to meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html">success criteria 1.4.13</a>. Michael pointed to <a href="https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html">this WAI-ARIA Authoring Practices demo</a> which provides more info on the necessary Javascript features. These are highly recommended additions for your final production solution.</p> </blockquote> <hr /> <p>If you've not used Sass, you may want to take five minutes to understand <a href="https://sass-lang.com/guide#topic-3">the nesting syntax of Sass</a> to most easily understand the code samples given.</p> <div class="heading-wrapper h2"> <h2 id="base-navigation-html">Base Navigation HTML</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#base-navigation-html" aria-labelledby="base-navigation-html"><span hidden="">#</span></a></div> <p>We will enhance this as we continue, but here's our starting structure:</p> <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">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Main Navigation<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>About<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>dropdown<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- aria-expanded needs managed with Javascript --></span> <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">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dropdown__title<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>sweets-dropdown<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span> Sweets <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <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>dropdown__menu<span class="token punctuation">"</span></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>sweets-dropdown<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Donuts<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Cupcakes<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Chocolate<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Bonbons<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Order<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <p>Overlooking the <code>button</code> for a minute, this is the semantic standard for navigation links. This structure is flexible to live anywhere on your page, so it could be a table of contents in your sidebar as easily as it is the main navigation.</p> <p>Right out the gate, we have implemented a few features specifically for accessibility:</p> <ol> <li><code>aria-label</code> on the <code>&lt;nav&gt;</code> to help identify it's purpose when assistive tech is used to navigate a page by landmarks</li> <li>Use of a <code>button</code> as a focusable, discoverable element to trigger the opening of the dropdown</li> <li><code>aria-controls</code> on the <code>.dropdown__title</code> that links to the id of the <code>.dropdown__menu</code> to associate it with the menu for assistive tech</li> <li><code>aria-expanded</code> on the <code>button</code> which in your final solution needs toggled via Javascript as noted in the demo mentioned at the beginning of this article</li> </ol> <blockquote> <p>As noted by Michael, use of a <code>button</code> element also allows Dragon Naturally Speaking users to say something like 'click button' to open the menu.</p> </blockquote> <p>Our (mostly) default starting appearance is as follows:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ph6ne7veudghpvnajgp6.png" alt="default list of links" /></p> <div class="heading-wrapper h2"> <h2 id="initial-navigation-styles">Initial Navigation Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#initial-navigation-styles" aria-labelledby="initial-navigation-styles"><span hidden="">#</span></a></div> <p>First, we'll give some container styles to <code>nav</code> and define it as a grid container. Then we'll remove default list styles from the <code>nav ul</code> and <code>nav ul li</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 1rem<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token selector">ul </span><span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token selector">li </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/awvgs6rxt2j1787b9lbi.png" alt="navigation list with list styles removed" /></p> <p>We've lost the hierarchical definition, but we can begin to bring it back with the following:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector">> ul </span><span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token selector">> li </span><span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>By using the child combinator selector <code>&gt;</code> we've defined that the top-level <code>ul</code> which is a direct child of <code>nav</code> should switch it's <code>grid-auto-flow</code> to <code>column</code> which effectively updates it to be along the <code>x-axis</code>. We then add margin to the top-level <code>li</code> elements for a bit more definition. Now, the future dropdown items are appearing contained below the &quot;Sweets&quot; menu and are more clearly its children:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/y6ikx5v9h84lm44cfksp.png" alt="nav list with direct child styles" /></p> <p>Next we'll add a touch of style first to all links as well as the <code>.dropdown__title</code>, then to only the top-level links in addition to the <code>.dropdown__title</code>. This is also where we clear out the native browser styles inherited for <code>button</code> elements.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Clear native browser button styles</span> <span class="token selector">.dropdown__title </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token selector">> ul </span><span class="token punctuation">{</span> <span class="token selector">> li </span><span class="token punctuation">{</span> <span class="token comment">// All links contained in the li</span> <span class="token selector">a, .dropdown__title </span><span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.125rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Only direct links contained in the li</span> <span class="token selector">> a, .dropdown__title </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1rem 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/pmnd1jz05g8u934r72lk.png" alt="updated link styles" /></p> <div class="heading-wrapper h2"> <h2 id="base-dropdown-styles">Base Dropdown Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#base-dropdown-styles" aria-labelledby="base-dropdown-styles"><span hidden="">#</span></a></div> <p>We have thus far been relying on element selectors, but we will bring in class selectors for the dropdown since there may be multiple in a given navigation list.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>We'll first style up the <code>.dropdown__menu</code> and its links to help identify it more clearly as we work through positioning and animation:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0.15em 0.25em <span class="token function">rgba</span><span class="token punctuation">(</span>black<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em 0<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 15ch<span class="token punctuation">;</span> <span class="token selector">a </span><span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #444<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8fgjgciuge2ts2k0nqbl.png" alt="dropdown__menu styles" /></p> <p>One of the clear issues is that the <code>.dropdown__menu</code> is affecting the <code>nav</code> container, which you can see from the grey <code>nav</code> background being present around the dropdown.</p> <p>We can start to fix this by adding <code>position: absolute</code> to the <code>.dropdown__menu</code> which takes it out of normal document flow:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jdvoh962drdgg0vu4bze.png" alt="menu with position absolute" /></p> <p>You can see it's aligned to the left and below of the parent list item. Depending on your design, this may be the desirable location.</p> <p>We're going to pull out a centering trick to align the menu central to the list item:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token comment">// Pull up to overlap the parent list item very slightly</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% <span class="token operator">-</span> 0.25rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Use the left from absolute position to shift the left side</span> <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token comment">// Use translateX to shift the menu 50% of it's width back to the left</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The magic of this centering technique is that the menu could be any width or even a dynamic width and it would center appropriately.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/rgd0anjvrugsq1x8mk9x.png" alt="centered dropdown__menu styles" /></p> <div class="heading-wrapper h2"> <h2 id="dropdown-reveal-styles">Dropdown Reveal Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#dropdown-reveal-styles" aria-labelledby="dropdown-reveal-styles"><span hidden="">#</span></a></div> <p>There are two primary triggers we want used to open the menu: <code>:hover</code> and <code>:focus</code>.</p> <p>However, traditional <code>:focus</code> will not persist the open state of the dropdown. Once the initial trigger loses focus, the keyboard focus may still move through the dropdown menu, but visually the menu would disappear.</p> <div class="heading-wrapper h3"> <h3 id="focus-within"><code>:focus-within</code></h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#focus-within" aria-labelledby="focus-within"><span hidden="">#</span></a></div> <p>There is an upcoming pseudo-class called <code>:focus-within</code> and it is precisely what we need to make it possible for this to be a CSS-only dropdown. As mentioned in the intro, it does require a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a> if you need to support IE &lt; Edge 79 (<a href="https://caniuse.com/#search=focus-within">you do</a>... for now).</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">From MDN</a>, italics mine to show the part we're going to benefit from:</p> <blockquote> <p>The <code>:focus-within</code> CSS pseudo-class represents an element that has received focus <em>or contains an element that has received focus</em>. In other words, it represents an element that is itself matched by the <code>:focus</code> pseudo-class <em>or has a descendant that is matched by <code>:focus</code></em>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="hide-the-dropdown-by-default">Hide the dropdown by default</h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#hide-the-dropdown-by-default" aria-labelledby="hide-the-dropdown-by-default"><span hidden="">#</span></a></div> <p>Before we can reveal the dropdown, we need to hide it, so we will use the hidden styles as the default state.</p> <p>Your first instinct may be <code>display: none</code> but that locks us out of gracefully animating the transition.</p> <p>Next, you might try simply <code>opacity: 0</code> which visibly hides it but leaves behind &quot;ghost links&quot; because the element still has computed height.</p> <p>Instead, we will use a combination of <code>opacity</code>, <code>transform</code>, and <code>visibilty</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotateX</span><span class="token punctuation">(</span>-90deg<span class="token punctuation">)</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> top center<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.3<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We add opacity but not all the way to 0 to enable a bit smoother effect later.</p> <p>And, we update our <code>transform</code> property to include <code>rotateX(-90deg)</code>, which will rotate the menu in 3D space to 90 degrees &quot;backwards&quot;. This effectively removes the height and will make for an interesting transition on reveal. Also you'll notice the <code>transform-origin</code> property which we add to update the point around which the transform is applied, versus the default of the horizontal and vertical center.</p> <p>Additionally, to meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/meaningful-sequence.html">success criteria 1.3.2</a>, the links should be hidden from screen reader users until they are visually displayed. We ensure this behavior by including <code>visibility: hidden</code> (thanks again to Michael for this tip!).</p> <p>Before we do the reveal, we need to add a <code>transition</code> property. We add it to the main <code>.dropdown__menu</code> rule so that it applies both on and off focus/hover, aka &quot;forwards&quot; and &quot;backwards&quot;.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transition</span><span class="token punctuation">:</span> 280ms all ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="revealing-the-dropdown">Revealing the dropdown</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#revealing-the-dropdown" aria-labelledby="revealing-the-dropdown"><span hidden="">#</span></a></div> <p>With all that prior setup, revealing the dropdown on both hover and focus can be accomplished as succinctly as:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>:hover, <span class="token parent important">&amp;</span>:focus-within </span><span class="token punctuation">{</span> <span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotateX</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">visibility</span><span class="token punctuation">:</span> visible<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>First, we reverse the <code>visibilty</code> (or the other properties would not work), and then we've reversed the <code>rotateX</code> by resetting to 0, and then bring the <code>opacity</code> all the way up to <code>1</code> for full visibility.</p> <p>Here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5z5zaa54czp54u7jleca.gif" alt="demo of reveal on focus and hover" /></p> <p>The <code>rotateX</code> property allows the appearance of the menu swinging in from the back, and <code>opacity</code> just makes it a little softer transition overall.</p> <blockquote> <p>Once again a note that for full accessibility, there is a need for Javascript to fully handle for keyboard assistive tech events that do not always trigger <code>:focus</code>. This means some sighted keyboard users may discover the dropdown links, but without a <code>:focus</code> event emitted, they will not see the dropdown menu actually open. Review the <a href="https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html">w3c demo</a> for how to finish incorporating Javascript in this solution.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="handling-hover-intent">Handling Hover Intent</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#handling-hover-intent" aria-labelledby="handling-hover-intent"><span hidden="">#</span></a></div> <p>If you've been at this web thing for a while, I'm hoping the following will make you go 🤯</p> <p>When I first began battling dropdown menus I was creating them primarily for IE7. On a big project, several team members asked something along the lines of &quot;can you stop the menu appearing if I'm just scrolling/mousing over the menu?&quot;. The solution I finally found after much Googling (including trying to come up with the right phrase to get what I was after) was the <a href="https://briancherne.github.io/jquery-hoverIntent/">hoverIntent jQuery plugin</a>.</p> <p>I needed to set that up because since we are using the <code>transition</code> property, we can also add a very slight delay. For general purposes, this will prevent the dropdown animation triggering for &quot;drive-by&quot; mouseovers.</p> <p>Order matters when we're defining all transition properties in one line, and the second numerical value in order will be picked up as the delay value:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.dropdown__menu</span> <span class="token punctuation">{</span> // ... existing styles <span class="token property">transition</span><span class="token punctuation">:</span> 280ms all 120ms ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Check out the results:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mmts717vg1uxyt8mivgq.gif" alt="demo of transition delay with mouseover" /></p> <p>It takes a pretty leisurely rollover to trigger the menu, which we can loosely infer as intent to open the menu. The delay is still short enough to not be consciously noticed prior to opening the menu, so it's a win!</p> <p>You may still choose to use Javascript to enhance this particularly if it's going to launch a &quot;mega menu&quot; that would be more disruptive, but this is still pretty delightful.</p> <div class="heading-wrapper h2"> <h2 id="dropdown-menu-indicator">Dropdown Menu Indicator</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#dropdown-menu-indicator" aria-labelledby="dropdown-menu-indicator"><span hidden="">#</span></a></div> <p>Hover intent is one thing, but really we need an additional cue to the user that this menu has additional options. An extremely common convention is a &quot;caret&quot; or &quot;down arrow&quot; mimicking the indicator of a native select element.</p> <p>To add this, we will update the <code>.dropdown__title</code> styles. We'll define it as an <code>inline-flex</code> container and then create an <code>:after</code> element that uses the border trick to create a downward arrow. We use a dash of <code>translateY()</code> to optically align it with our text:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector">.dropdown__title </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-flex<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>:after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 0.35rem solid transparent<span class="token punctuation">;</span> <span class="token property">border-top-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>blue<span class="token punctuation">,</span> 0.45<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0.15em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/t6fj8oode9wkn736dyq7.png" alt="dropdown caret indicator" /></p> <div class="heading-wrapper h3"> <h3 id="closing-the-menu-on-mobile">Closing the menu on mobile</h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#closing-the-menu-on-mobile" aria-labelledby="closing-the-menu-on-mobile"><span hidden="">#</span></a></div> <p>Here's another place where ultimately you may have to enhance with Javascript.</p> <p>To keep it CSS-only, and acceptable for non-application websites, you need to apply <code>tabindex=&quot;-1&quot;</code> on the body, effectively allowing any clicks outside of the menu to remove focus from it and allowing it to close.</p> <p>This is a bit of a stretch - and it may be a little frustrating to users - so you may want to enhance this to hide on scroll as well with Javascript especially if you define the <code>nav</code> to use <code>position: sticky</code> and scroll with the user.</p> <div class="heading-wrapper h2"> <h2 id="final-result">Final Result</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#final-result" aria-labelledby="final-result"><span hidden="">#</span></a></div> <p>Here's the final result with a bit of extra styling including an arrow to more visually connect the menu to the link item, custom focus states on all the nav links, and <code>position: sticky</code> on the <code>nav</code>:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="MWaJePa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> Responsive Image Gallery With Animated Captions 2020-04-21T00:00:00Z https://moderncss.dev/responsive-image-gallery-with-animated-captions/ <style>.demo .demo--content { background-color: transparent; padding: 1rem; }</style> <p>Responsively resizing images is a common need, and modern CSS provides tools for ensuring a consistent <code>aspect-ratio</code> while not distorting the images. And grid gives us flexibility for a gallery layout as well as positioning multiple elements in a shared space.</p> <p>This responsive gallery technique explores using:</p> <ul> <li><code>object-fit</code> for responsive image scaling</li> <li><code>aspect-ratio</code> for consistent image sizes</li> <li>A CSS Grid trick to replace absolute positioning</li> <li>CSS transforms for animated effects</li> <li>handling for touch devices</li> <li>respecting reduced motion</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="gallery-html">Gallery HTML</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#gallery-html" aria-labelledby="gallery-html"><span hidden="">#</span></a></div> <p>Here is our initial HTML, which is an <code>ul</code> where each <code>li</code> contains a <code>figure</code> with the image and <code>figcaption</code>:</p> <pre class="language-html"><code class="language-html"><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>gallery<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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/550/300<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Candy canes ice cream<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/400<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Ice cream biscuit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/600/450<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Cream biscuit marzipan<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <blockquote> <p>What's that <code>role=&quot;list&quot;</code> doing there? It <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">ensures assistive technology still interprets the element as a list</a> after we remove list styling with CSS.</p> </blockquote> <p>I've used different image sizes both to showcase how <code>object-fit</code> works in terms of fitting its container, and also to lessen the chance of duplicate images from the <a href="https://picsum.photos/">picsum</a> service.</p> <p>Note that due to using a random image service, I haven't provided full <code>alt</code> descriptions or real <code>figcaption</code> text for these demo images. Ideally you should write <code>alt</code> that describes the image, and use <code>figcaption</code> to provide context for the image as a figure. I recommend this resource to <a href="https://thoughtbot.com/blog/alt-vs-figcaption">learn more about the importance of writing <code>alt</code> and <code>figcaption</code></a>.</p> <div class="heading-wrapper h2"> <h2 id="base-gallery-styles">Base Gallery Styles</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#base-gallery-styles" aria-labelledby="base-gallery-styles"><span hidden="">#</span></a></div> <p>Since we've used a list, we need to remove default list styles, and we will also set the list up as a grid container. These initial styles achieve placing our list items in a row and ensures the images stay in their grid columns but does not resize them.</p> <details open=""> <summary>CSS for "gallery class"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>20ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-12 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-12 img { display: block; width: 100%; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-12" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="gallery-card-and-image-styles">Gallery Card and Image Styles</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#gallery-card-and-image-styles" aria-labelledby="gallery-card-and-image-styles"><span hidden="">#</span></a></div> <p>If you're like me and have tried to do this in years past, you probably threw your rollerball mouse across the room trying to figure out why <code>position: absolute</code> wasn't playing nicely with your jQuery animations.</p> <p>CSS Grid and CSS transforms are here to save the day! 🎉</p> <p>We're going to setup the <code>figure</code> to use grid display, and also define a custom property to hold the desired image height. And we'll give it a background in case the image is a little slow to load.</p> <details open=""> <summary>CSS for "Base figure display styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token property">--gallery-height</span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token comment">/* reset figure default margin */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-height<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>200<span class="token punctuation">,</span> 85%<span class="token punctuation">,</span> 2%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-182 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-182 img { display: block; width: 100%; } .gallery-182 figure { --gallery-height: 15rem; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-182" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>Next, we apply <code>object-fit</code> to the image along with <code>width: 100%</code> and pull in the custom property for the height so that it scales to the size of the figure. The magic of <code>object-fit: cover</code> is that no distortion occurs.</p> <details open=""> <summary>CSS for "Image display styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-height<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-593 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-593 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-593 figure { --gallery-height: 15rem; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-593" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>If you resize the demo, you'll see that the img is now behaving much as if it were applied as a <code>background-image</code> and using <code>background-size: cover</code>. The <code>img</code> tag is acting like a container for it's own contents.</p> <blockquote> <p>For a helpful intro to <code>object-fit</code> for responsive image scaling, check out <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">this earlier post from this series</a>. You might also like my <a href="https://5t3ph.dev/egobjfit">3-minute free egghead lesson on <code>object-fit</code></a>.</p> </blockquote> <p>We can improve the image sizing by upgrading to using <code>aspect-ratio</code> when supported using the native CSS feature <code>@supports</code>. When it is supported, we'll drop the <code>height</code> and swap for defining the <code>aspect-ratio</code> to use. This allows us to have more consistently sized images across viewports.</p> <details open=""> <summary>CSS for "Use aspect-ratio with @supports"</summary> <pre class="language-css"><code class="language-css"><span class="token comment">/* Add aspect-ratio custom property */</span> <span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token property">--gallery-aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.gallery figure, .gallery img</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-aspect-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Remove height to prevent distorting aspect-ratio */</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-140 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-140 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-140 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } @supports (aspect-ratio: 1) { .gallery-140 figure, .gallery-140 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-140" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="positioning-the-caption">Positioning the Caption</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#positioning-the-caption" aria-labelledby="positioning-the-caption"><span hidden="">#</span></a></div> <p>Now at this point, the caption has flowed naturally according to DOM order below the image. This is also due to default CSS grid behavior because it's assumed that it should be in its own &quot;cell&quot; and by default grid items flow down the y-axis in rows.</p> <p>To resolve this, we create a named <code>grid-template-areas</code> for the <code>figure</code>, and assign both the <code>img</code> and <code>figcaption</code> to live there. Then, we'll use grid positioning to set <code>place-items: end</code> on the card to move the caption to the bottom right of the &quot;cell&quot;.</p> <details open=""> <summary>CSS for "Styles to position the figcaption"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"card"</span><span class="token punctuation">;</span> <span class="token property">place-items</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure > *</span> <span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> card<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-74 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-74 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-74 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-74 figure, .gallery-74 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-74 figure > * { grid-area: card; } .gallery-74 figcaption { transform: translateY(100%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-74" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>You may notice the caption is also no longer visible, partly from adding <code>overflow: hidden</code> to the <code>figure</code>. Then to place the caption, we used CSS transforms to set the initial position outside the <code>figure</code>. A value of <code>100%</code> for translate will move the element <code>100%</code> relative to the axis it's placed on. So, <code>translateY(100%)</code> effectively moves the caption &quot;down&quot; out of the initial view.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="animating-the-caption">Animating the Caption</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#animating-the-caption" aria-labelledby="animating-the-caption"><span hidden="">#</span></a></div> <p>Our animation will trigger on hover, and we want it to smoothly animate in and back out again. This requires setting up the <code>transition</code> property.</p> <p>We'll define that we expect a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition">transition</a> on the <code>transform</code> property, and that the transition duration should be <code>800ms</code> and use the <code>ease-in</code> timing function.</p> <p>The <code>:hover</code> styles will actually be placed on the <code>figure</code> since it is the containing element, so we'll also add a <code>transform</code> definition that moves the caption back to it's inherent starting point by returning it to position <code>0</code> on the y-axis.</p> <details open=""> <summary>CSS for "Style and animate the figcaption"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms ease-in<span class="token punctuation">;</span> <span class="token comment">/* Visual styles for the caption */</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px 0 0 0<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 100% / 87%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure:hover figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-287 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-287 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-287 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-287 figure, .gallery-287 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-287 figure > * { grid-area: card; } .gallery-287 figcaption { transform: translateY(100%); transition: transform 800ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-287 figure:hover figcaption { transform: translateY(0); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-287" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>And ta-da! We have a basic animated caption.</p> <div class="heading-wrapper h2"> <h2 id="ken-burns-image-effect">Ken Burns image effect</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#ken-burns-image-effect" aria-labelledby="ken-burns-image-effect"><span hidden="">#</span></a></div> <p>You may not have known the name, but you've seen the effect: a slow, smooth pan and zoom combo of a still image, so named due to being popularized by documentary filmmaker <a href="https://en.wikipedia.org/wiki/Ken_Burns">Ken Burns</a>.</p> <p>Using the principles we've already covered with the <code>transition</code> and <code>tranform</code> properties, we can again combine them on the <code>img</code> to add this effect on hover as well.</p> <p>We add an additional value to <code>transform</code> to set the default <code>scale</code> to 0 since on hover we'll be scaling it up so we need to set the point it starts from. We're using a generous duration of <code>1200ms</code> for the transition to take place in order to create a smooth pan and zoom effect.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 1200ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next we add the <code>:hover</code> transition into the <code>figure</code> rule, adding both a bit more of a scale up for the zoom-in effect, in addition to pulling it back left on the x-axis to <code>-8%</code> and also a bit up on the y-axis with <code>-3%</code>. You can adjust the translate values to your taste.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure:hover img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>There's one more thing, which is that we have set our transition durations with a <code>400ms</code> difference. We can add that value as a delay for the caption. Be aware that the delay applies prior to the transition on hover, and at the end of the transition out off-hover. Personally I like this effect since it means that in both directions the animations end together.</p> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token comment">/* update to add the 400ms delay */</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms 400ms ease-in<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> <p>Altogether, here is our gallery with the Ken Burns effect on the image and caption.</p> <details open=""> <summary>CSS for "Ken Burns style animated figures"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 1200ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure:hover img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token comment">/* added 400ms delay */</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms 400ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-68 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-68 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-68 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-68 figure, .gallery-68 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-68 figure > * { grid-area: card; } .gallery-68 figcaption { transform: translateY(100%); transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-68 figure:hover figcaption { transform: translateY(0); } .gallery-68 figure:hover img { transform: scale(1.3) translate(-8%, -3%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-68" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="dont-forget-about-focus">Don't forget about <code>:focus</code></h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#dont-forget-about-focus" aria-labelledby="dont-forget-about-focus"><span hidden="">#</span></a></div> <p>Hover is fine for mouse-users, but what about those who for various reasons use primarily their keyboard to navigate?</p> <p>The <code>li</code> element isn't inherently a focusable element, so just adding <code>:focus</code> styles will not change behavior.</p> <p>We have two options:</p> <ul> <li>If you plan to link the images anyway, wrap the <code>figure</code> with a link element and hook <code>:focus</code> styles to that</li> <li>If a link isn't needed, apply <code>tabindex=&quot;0&quot;</code> to each <code>figure</code> which will enable them as focusable elements</li> </ul> <p>We'll use the <code>tabindex</code> approach for this demo.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span></code></pre> <p>You can test this by tabbing and you will notice the standard focus halo outline.</p> <p>We'll customize the outline and also update the rules to apply the same <code>:hover</code> behavior on <code>:focus</code>.</p> <details open=""> <summary>CSS for "Reveal captions on figure:focus"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figure:focus</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid white<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">outline-offset</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="token selector">.gallery figure:hover figcaption, .gallery figure:focus figcaption</span> <span class="token punctuation">{</span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="token selector">.gallery figure:hover img, .gallery figure:focus img</span> <span class="token punctuation">{</span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-748 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-748 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-748 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-748 figure, .gallery-748 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-748 figure > * { grid-area: card; } .gallery-748 figcaption { transform: translateY(100%); transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-748 figure:hover figcaption, .gallery-748 figure:focus figcaption { transform: translateY(0); } .gallery-748 figure:hover img, .gallery-748 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-748 figure:focus { outline: 2px solid white; outline-offset: 2px; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-748" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="handling-for-touch-devices">Handling for touch devices</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#handling-for-touch-devices" aria-labelledby="handling-for-touch-devices"><span hidden="">#</span></a></div> <p>We've made a large assumption so far which is that users interacting with our gallery have a hover capable device <em>and</em> the motor abilities required to perform a &quot;hover&quot; on an element.</p> <p>While the current hover experience somewhat works on a touch device, if you upgrade the gallery to use links it's likely the caption wouldn't have time to show prior to the navigation event. So, let's instead change our strategy to only enable the animated links for hover-capable devices and set the default to display them.</p> <p>This is done with a media query combo to detect both hover and a &quot;fine&quot; pointing device which is likely to mean the user is primarily using a mouse, possibly a stylus. The keyword here is &quot;likely&quot; as there are devices capable of touch sometimes, and more &quot;fine&quot; pointers other times. For more info to help you make an informed decision, check out this excellent <a href="https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/">overview of interaction media features</a> from Patrick H. Lauke.</p> <p>We'll remove the <code>transform</code> on the <code>figcaption</code> and instead only apply it if this media query combo is valid. If you're on a touch device you'll likely see the captions by default in this next demo, or you can emulate a mobile or touch device using your browser dev tools.</p> <details open=""> <summary>CSS for "Only animate captions for non-touch devices"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span></del> <span class="highlight-line"> <span class="token comment">/* provide stacking context */</span></span> <span class="highlight-line"> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">any-hover</span><span class="token punctuation">:</span> hover<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">any-pointer</span><span class="token punctuation">:</span> fine<span class="token punctuation">)</span></span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token punctuation">}</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-202 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-202 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-202 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-202 figure, .gallery-202 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-202 figure > * { grid-area: card; } .gallery-202 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-202 figure:hover figcaption, .gallery-202 figure:focus figcaption { transform: translateY(0); } .gallery-202 figure:hover img, .gallery-202 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-202 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-202 figcaption { transform: translateY(100%); } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-202" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="respecting-user-motion-preferences">Respecting user motion preferences</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#respecting-user-motion-preferences" aria-labelledby="respecting-user-motion-preferences"><span hidden="">#</span></a></div> <p>Some users may have a need for a &quot;reduced motion&quot; experience, which we can handle by way of a media query as well.</p> <p>The prefers-reduced-motion media query will let us remove the transition of the caption and image when the user has updated their system settings to request reduced motion. You can <a href="https://12daysofweb.dev/2021/preference-queries/">learn more about this preference media query in my overview</a>.</p> <p>When a reduced motion setting is true, we'll remove the associated <code>transition</code> and <code>transform</code> values. The result will be that the image does not have the Ken Burns effect and the caption appears instantly with no transition.</p> <details open=""> <summary>CSS for "Remove animation for prefers-reduced-motion"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.gallery *</span> <span class="token punctuation">{</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> none <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transition-delay</span><span class="token punctuation">:</span> 0ms<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-41 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-41 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-41 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-41 figure, .gallery-41 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-41 figure > * { grid-area: card; } .gallery-41 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-41 figure:hover figcaption, .gallery-41 figure:focus figcaption { transform: translateY(0); } .gallery-41 figure:hover img, .gallery-41 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-41 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-41 figcaption { transform: translateY(100%); } } @media (prefers-reduced-motion: reduce) { .gallery-41 * { transition-duration: 0ms !important; } .gallery-41 img { transform: none !important; } .gallery-41 figcaption { transition-delay: 0ms; } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-41" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="optional-vignette">Optional: Vignette</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#optional-vignette" aria-labelledby="optional-vignette"><span hidden="">#</span></a></div> <p>Another hallmark of the Ken Burns style is a vignette - the soft black gradient on the borders of the image. We can accomplish this with an inset <code>box-shadow</code>. However, an inset <code>box-shadow</code> will not work on the image element directly, so instead we apply it on an <code>:after</code> pseudo element of the <code>figure</code>:</p> <p>The vignette is positioned by applying it to the single named <code>grid-area</code> and ensuring it has a <code>height</code> and <code>width</code> to take up the whole card in addition to relative positioning to stack it above the image.</p> <details open=""> <summary>CSS for "Vignette effect"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> card<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 2rem 1rem <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 0% / 65%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-288 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-288 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-288 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-288 figure, .gallery-288 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-288 figure > * { grid-area: card; } .gallery-288 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-288 figure:hover figcaption, .gallery-288 figure:focus figcaption { transform: translateY(0); } .gallery-288 figure:hover img, .gallery-288 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-288 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-288 figcaption { transform: translateY(100%); } } @media (prefers-reduced-motion: reduce) { .gallery-288 * { transition-duration: 0ms !important; } .gallery-288 img { transform: none !important; } .gallery-288 figcaption { transition-delay: 0ms; } } /* Vignette */ .gallery-288 figure::after { content: ""; grid-area: card; width: 100%; height: 100%; box-shadow: inset 0 0 2rem 1rem hsl(0 0% 0% / 65%); position: relative; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-288" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>Choose the &quot;Open in CodePen&quot; option to generate a new CodePen that includes the final styles created for this component.</p> <form action="https://codepen.io/pen/define" method="POST" target="_blank"> <input type="hidden" name="data" value='{"title":"Modern CSS Solutions - Responsive Image Gallery With Animated Captions","description":"Generated from: ModernCSS.dev/responsive-image-gallery-with-animated-captions/","tags":["moderncss"],"editors":"110","layout":"left","html":"<!-- Modern CSS Solutions - Responsive Image Gallery With Animated Captions\nGenerated from: ModernCSS.dev/responsive-image-gallery-with-animated-captions/ -->\n<ul class=\"gallery\" role=\"list\">\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/550\" />\n <figcaption>Candy canes ice cream</figcaption>\n </figure>\n </li>\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/400\" />\n <figcaption>Ice cream biscuit</figcaption>\n </figure>\n </li>\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/600\" />\n <figcaption>Cream biscuit marzipan</figcaption>\n </figure>\n </li>\n</ul>\n","html_pre_processor":"none","css":"/* Box sizing rules */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* Remove default margin */\nbody,\nh1,\nh2,\nh3,\nh4,\np {\n margin: 0;\n}\n\n/* Set core body defaults */\nbody {\n min-height: 100vh;\n text-rendering: optimizeSpeed;\n line-height: 1.5;\n font-family: system-ui, sans-serif;\n}\n\n/* Make images easier to work with */\nimg {\n display: block;\n max-width: 100%;\n}\n\n/***\n 🟣 Modern CSS Solutions Demo Styles\n */\n\n.gallery {\n list-style: none;\n padding: 0;\n margin: 0 auto;\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr));\n gap: 1rem;\n}\n\n.gallery img {\n display: block;\n width: 100%;\n object-fit: cover;\n height: var(--gallery-height);\n transform: scale(1) translate(0, 0);\n transition: transform 1200ms ease-in;\n}\n\n.gallery figure {\n --gallery-height: 15rem;\n --gallery-aspect-ratio: 4/3;\n\n /* reset figure default margin */\n margin: 0;\n height: var(--gallery-height);\n background-color: hsl(200, 85%, 2%);\n\n display: grid;\n grid-template-areas: \"card\";\n place-items: end;\n border-radius: 0.5rem;\n overflow: hidden;\n}\n\n@supports (aspect-ratio: 1) {\n .gallery figure,\n .gallery img {\n aspect-ratio: var(--gallery-aspect-ratio);\n /* Remove height to prevent distorting aspect-ratio */\n height: auto;\n }\n}\n\n.gallery figure > * {\n grid-area: card;\n}\n\n.gallery figcaption {\n transition: transform 800ms 400ms ease-in;\n\n /* Visual styles for the caption */\n padding: 0.25em 0.5em;\n border-radius: 4px 0 0 0;\n background-color: hsl(0 0% 100% / 87%);\n /* provide stacking context */\n z-index: 1;\n}\n\n.gallery figure:hover figcaption,\n.gallery figure:focus figcaption {\n transform: translateY(0);\n}\n\n.gallery figure:hover img,\n.gallery figure:focus img {\n transform: scale(1.3) translate(-8%, -3%);\n}\n\n.gallery figure:focus {\n outline: 2px solid white;\n outline-offset: 2px;\n}\n\n@media (any-hover: hover) and (any-pointer: fine) {\n .gallery figcaption {\n transform: translateY(100%);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gallery * {\n transition-duration: 0ms !important;\n }\n\n .gallery img {\n transform: none !important;\n }\n\n .gallery figcaption {\n transition-delay: 0ms;\n }\n}\n\n/* Vignette */\n.gallery figure::after {\n content: \"\";\n grid-area: card;\n width: 100%;\n height: 100%;\n box-shadow: inset 0 0 2rem 1rem hsl(0 0% 0% / 65%);\n position: relative;\n}\n","css_pre_processor":"scss","css_starter":"neither","css_prefix":"autoprefixer","head":"<meta name=&apos;viewport&apos; content=&apos;width=device-width, initial-scale=1&apos;>"}' /> <button class="button" type="submit" data-name="Responsive Image Gallery With Animated Captions" aria-label="Open Responsive Image Gallery With Animated Captions in CodePen"><span class="button__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false"> <path d="M32 10.909l-0.024-0.116-0.023-0.067c-0.013-0.032-0.024-0.067-0.040-0.1-0.004-0.024-0.020-0.045-0.027-0.067l-0.047-0.089-0.040-0.067-0.059-0.080-0.061-0.060-0.080-0.060-0.061-0.040-0.080-0.059-0.059-0.053-0.020-0.027-14.607-9.772c-0.463-0.309-1.061-0.309-1.523 0l-14.805 9.883-0.051 0.053-0.067 0.075-0.049 0.060-0.067 0.080c-0.027 0.023-0.040 0.040-0.040 0.061l-0.067 0.080-0.027 0.080c-0.027 0.013-0.027 0.053-0.040 0.093l-0.013 0.067c-0.025 0.041-0.025 0.081-0.025 0.121v9.996c0 0.059 0.004 0.12 0.013 0.18l0.013 0.061c0.007 0.040 0.013 0.080 0.027 0.115l0.020 0.067c0.013 0.036 0.021 0.071 0.036 0.1l0.029 0.067c0 0.013 0.020 0.053 0.040 0.080l0.040 0.053c0.020 0.013 0.040 0.053 0.060 0.080l0.040 0.053 0.053 0.053c0.013 0.017 0.013 0.040 0.040 0.040l0.080 0.056 0.053 0.040 0.013 0.019 14.627 9.773c0.219 0.16 0.5 0.217 0.76 0.217s0.52-0.080 0.76-0.24l14.877-9.875 0.069-0.077 0.044-0.060 0.053-0.080 0.040-0.067 0.040-0.093 0.021-0.069 0.040-0.103 0.020-0.060 0.040-0.107v-10c0-0.067 0-0.127-0.021-0.187l-0.019-0.060 0.059 0.004zM16.013 19.283l-4.867-3.253 4.867-3.256 4.867 3.253-4.867 3.253zM14.635 10.384l-5.964 3.987-4.817-3.221 10.781-7.187v6.424zM6.195 16.028l-3.443 2.307v-4.601l3.443 2.301zM8.671 17.695l5.964 3.987v6.427l-10.781-7.188 4.824-3.223v-0.005zM17.387 21.681l5.965-3.973 4.817 3.227-10.783 7.187v-6.427zM25.827 16.041l3.444-2.293v4.608l-3.444-2.307zM23.353 14.388l-5.964-3.988v-6.44l10.78 7.187-4.816 3.224z"></path> </svg></span> Open in CodePen</button> </form> <div class="heading-wrapper h2"> <h2 id="next-steps-upgrade-from-a-basic-img">Next steps: upgrade from a basic <code>img</code></h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#next-steps-upgrade-from-a-basic-img" aria-labelledby="next-steps-upgrade-from-a-basic-img"><span hidden="">#</span></a></div> <p>In this simple gallery example, we just used a basic <code>img</code> element. If you'd like to learn how to use modern image formats and improve performance of your images, review my <a href="https://12daysofweb.dev/2021/image-display-elements/">guide to image display elements</a>.</p> Totally Custom List Styles 2020-04-18T00:00:00Z https://moderncss.dev/totally-custom-list-styles/ <p>This tutorial will show how to use CSS grid layout for easy custom list styling in addition to:</p> <ul> <li>Data attributes as the content of pseudo elements</li> <li>CSS counters for styling ordered lists</li> <li>CSS custom variables for per-list item styling</li> <li>Responsive multi-column lists</li> </ul> <blockquote> <p><strong>Update</strong>: The <code>::marker</code> pseudo selector is now well supported in modern browsers. While this tutorial includes handy CSS tips for the items listed above, you may want to <a href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker">jump to the <code>::marker</code> solution</a></p> </blockquote> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="list-html">List HTML</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#list-html" aria-labelledby="list-html"><span hidden="">#</span></a></div> <p>First we'll setup our HTML, with one <code>ul</code> and one <code>li</code>. I've included a longer bullet to assist in checking alignment, spacing, and line-heihgt.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Cake ice cream sweet sesame snaps dragée cupcake wafer cookie<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Cake ice cream sweet sesame snaps dragée cupcake wafer cookie<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span></code></pre> <p>Note the use of <code>role=&quot;list&quot;</code>. At first, it may seem extra, but we are going to remove the inherent list style with CSS. While CSS doesn't often affect the semantic value of elements, <code>list-style: none</code> can remove list semantics for some screen readers. The easiest fix is to define the <code>role</code> attribute to reinstate those semantics. You can learn more from <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">this article</a> from Scott O'Hara.</p> <div class="heading-wrapper h2"> <h2 id="base-list-css">Base List CSS</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#base-list-css" aria-labelledby="base-list-css"><span hidden="">#</span></a></div> <p>First we add a reset of list styles in addition to defining them as a grid with a gap.</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>gap</code> benefit is adding space between <code>li</code>, taking the place of an older method such as <code>li + li { margin-top: ... }</code>.</p> <p>Next, we'll prepare the list items:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 0 1fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1.75em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.25<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We've also set list items up to use grid. And we've upgraded an older &quot;hack&quot; of using <code>padding-left</code> to leave space for an absolute positioned pseduo element with a combo of a <code>0</code> width first column and <code>gap</code>. We'll see how that works in a moment. Then we use <code>align-items: start</code> instead of the default of <code>stretch</code>, and apply some type styling.</p> <div class="heading-wrapper h2"> <h2 id="ul-data-attributes-for-emoji-bullets">UL: Data attributes for emoji bullets</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ul-data-attributes-for-emoji-bullets" aria-labelledby="ul-data-attributes-for-emoji-bullets"><span hidden="">#</span></a></div> <p>Now, this may not exactly be a scalable solution, but for fun we're going to add a custom data attribute that will define an emoji to use as the bullet for each list item.</p> <p>First, let's update the <code>ul</code> HTML:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span> Cake ice cream sweet sesame snaps dragée cupcake wafer cookie <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <p>And to apply the emojis as bullets, we use a pretty magical technique where data attributes can be used as the value of the <code>content</code> property for pseudo elements:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ul li::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-icon<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Make slightly larger than the li font-size but smaller than the li gap */</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's the result, with the <code>::before</code> element inspected to help illustrate how the grid is working:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/lr1hu2lcffytfcb9d0vk.png" alt="ul styled list elements" /></p> <p>The emoji still is allowed to take up width to be visible, but effectively sits in the gap. You can experiment with setting the first <code>li</code> grid column to <code>auto</code> which will cause gap to fully be applied between the emoji column and the list text column.</p> <div class="heading-wrapper h2"> <h2 id="ol-css-counters-and-css-custom-variables">OL: CSS counters and CSS custom variables</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ol-css-counters-and-css-custom-variables" aria-labelledby="ol-css-counters-and-css-custom-variables"><span hidden="">#</span></a></div> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters">CSS counters</a> have been a viable solution <a href="https://caniuse.com/#search=counter">since IE8</a>, but we're going to add an extra flourish of using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS custom variables</a> to change the background color of each number as well.</p> <p>We'll apply the CSS counter styles first, naming our counter <code>orderedlist</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">counter-reset</span><span class="token punctuation">:</span> orderedlist<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">ol li::before</span> <span class="token punctuation">{</span> <span class="token property">counter-increment</span><span class="token punctuation">:</span> orderedlist<span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">counter</span><span class="token punctuation">(</span>orderedlist<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This achieves the following, which doesn't look much different than the default <code>ol</code> styling:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wb46m4n76s7p4tomridj.png" alt="ol with counter" /></p> <p>Next, we can apply some base styling to the counter numbers:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Add to li::before rule */</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"Indie Flower"</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 0.75<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">padding-top</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span></code></pre> <p>First, we apply a Google font and bump up the <code>font-size</code>. The <code>line-height</code> is half of the applied <code>line-height</code> of the <code>li</code> (at least that's what worked for this font, it may be a bit of a magic number). It aligns the number where we would like in relation to the main <code>li</code> text content.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Then, we need to specify an explicit width. If not, the background will not appear even though the text will.</p> <p>Padding is added to fix the alignment of the text against the background.</p> <p>Now we have this:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/kcz28nyz3ly8fuvg57n9.png" alt="ol with additional styles" /></p> <p>That's certainly feeling more custom, but we'll push it a bit more by swapping the <code>background-color</code> to a CSS custom variable, like so:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">--li-bg</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">ol li::before</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--li-bg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It will appear the same until we add inline styles to the second and third <code>li</code> to update the variable value:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--li-bg</span><span class="token punctuation">:</span> darkcyan</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> Cake ice cream sweet sesame snaps dragée cupcake wafer cookie <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--li-bg</span><span class="token punctuation">:</span> navy</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span></code></pre> <p>And here's the final <code>ul</code> and <code>ol</code> all put together:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="WNQwEjz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="upgrade-your-algos-multi-column-lists">Upgrade your algos: Multi-column lists</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#upgrade-your-algos-multi-column-lists" aria-labelledby="upgrade-your-algos-multi-column-lists"><span hidden="">#</span></a></div> <p>Our example only had 3 short list items, but don't forget we applied grid to the base <code>ol</code> and <code>ul</code>.</p> <p>Whereas in a previous life I have done fun things with modulus in PHP to split up lists and apply extra classes to achieve evenly divided multi-column lists.</p> <p>With CSS grid, you can now apply it in three lines with inherent responsiveness, equal columns, and respect to content line length:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token comment">/* adjust the `min` value to your context */</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fill<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>22ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Applying to our existing example (be sure to remove the <code>max-width</code> on the <code>li</code> first) yields:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z9ty6z0n1fe1gu78tbis.png" alt="multi-column lists" /></p> <p>You can toggle this view by updating the <code>$multicolumn</code> variable in Codepen to <code>true</code>.</p> <div class="heading-wrapper h2"> <h2 id="gotcha-more-than-plain-text-as-li-content">Gotcha: More than plain text as <code>li</code> content</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#gotcha-more-than-plain-text-as-li-content" aria-labelledby="gotcha-more-than-plain-text-as-li-content"><span hidden="">#</span></a></div> <p>If you have more than plain text inside the <code>li</code> - including something like an innocent <code>&lt;a&gt;</code> - our grid template will break.</p> <p>However, it's a very easy solve - wrap the <code>li</code> content in a <code>span</code>. Our grid template doesn't care what the elements are, but it does only expect two elements, where the pseudo element counts as the first.</p> <div class="heading-wrapper h2"> <h2 id="upgrading-to-css-marker">Upgrading to CSS Marker</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker" aria-labelledby="upgrading-to-css-marker"><span hidden="">#</span></a></div> <p>During the months after this article was originally posted, support for <code>::marker</code> became much better across all modern browsers.</p> <p>The <code>::marker</code> pseudo selector allows directly changing and styling the <code>ol</code> or <code>ul</code> list bullet/numerical.</p> <p>We can fully replace the solution for <code>ul</code> bullets using <code>::marker</code> but we have to downgrade our <code>ol</code> solution because there are only a few properties allowed for <code>::marker</code>:</p> <ul> <li><code>animation-*</code></li> <li><code>color</code></li> <li><code>content</code></li> <li><code>direction</code></li> <li><code>font-*</code></li> <li><code>transition-*</code></li> <li><code>unicode-bidi</code></li> <li><code>white-space</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="unordered-list-style-with-marker">Unordered List Style With <code>::marker</code></h3> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#unordered-list-style-with-marker" aria-labelledby="unordered-list-style-with-marker"><span hidden="">#</span></a></div> <p>Since <code>content</code> is still an allowed property, we can keep our <code>data-icon</code> solution for allowing custom emoji markers 🎉</p> <p>We just need to swap <code>::before</code> to <code>::marker</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ul li::marker</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-icon<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then remove the no longer needed grid properties from the <code>li</code> and add back in some <code>padding</code> to replace the removed <code>gap</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token comment">/* replace the grid properties with: */</span> <span class="token property">padding-left</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Finally, we previously removed <code>margin</code> but we need to add back in some left margin to ensure space for the <code>::marker</code> to prevent it being cut off due to overflow:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* update in existing rule */</span> <span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 0 0 2em<span class="token punctuation">;</span> <span class="token comment">/* ...existing styles */</span> <span class="token punctuation">}</span></code></pre> <p>And the visual results is identical to our previous solution, as you can see in <a href="https://moderncss.dev/totally-custom-list-styles/#::marker-demo">the demo</a>.</p> <div class="heading-wrapper h3"> <h3 id="ordered-list-style-with-marker">Ordered List Style With <code>::marker</code></h3> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ordered-list-style-with-marker" aria-labelledby="ordered-list-style-with-marker"><span hidden="">#</span></a></div> <p>For our ordered list, we can now switch and take advantage of the built-in counter.</p> <p>We also have to drop our <code>background-color</code> and <code>border-radius</code> so we'll swap to using our custom property for the <code>color</code> value. And we'll change our custom property name to <code>--marker-color</code> for clarity.</p> <p>So our reduced styles are as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">--marker-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li::marker</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">counter</span><span class="token punctuation">(</span>list-item<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"Indie Flower"</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--marker-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>Don't forget to update the CSS custom property name in the HTML, too!</em></p> <blockquote> <p><strong>Watch out for this gotcha</strong>: Changing the <code>display</code> property for <code>li</code> will <em>remove</em> the <code>::marker</code> pseudo element. So if you need a different display type for list contents, you'll need to apply it by nesting an additional wrapping element.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="marker-demo"><code>::marker</code> Demo</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#marker-demo" aria-labelledby="marker-demo"><span hidden="">#</span></a></div> <p>Here's our updated custom list styles that now use <code>::marker</code>.</p> <p>Be sure to check for <a href="https://caniuse.com/?search=marker">current browser support</a> to decide which custom list style solution to use based on your unique audience! You may want to choose to use <code>::marker</code> as a progressive enhancement from one of the previously demonstrated solutions.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="KKgqeNB" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>For more details on using <code>::marker</code>, check out <a href="https://web.dev/css-marker-pseudo-element/">this excellent article</a> by Adam Argyle.</p> </blockquote> Pure CSS Smooth-Scroll "Back to Top " 2020-04-16T00:00:00Z https://moderncss.dev/pure-css-smooth-scroll-back-to-top/ <p>&quot;Back to top&quot; links may not be in use often these days, but there are two modern CSS features that the technique demonstrates well:</p> <ul> <li><code>position: sticky</code></li> <li><code>scroll-behavior: smooth</code></li> </ul> <p>I first fell in love with &quot;back to top&quot; links - and then learned how to do them with jQuery - on one of the most gorgeous sites of 2011: <a href="https://web.archive.org/web/20110413163553/https://webdesignerwall.com/tutorials/animated-scroll-to-top">Web Designer Wall</a>.</p> <p>The idea is to provide the user with a &quot;jump link&quot; to scroll back to the top of the website and was often used on blogs of yore.</p> <p>Here's what we will learn to achieve:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/e5vl0sijw6j0zrmiddc6.gif" alt="demo of &quot;back to top&quot; link" /></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="about-position-sticky">About <code>position: sticky</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#about-position-sticky" aria-labelledby="about-position-sticky"><span hidden="">#</span></a></div> <p>This newer position value is described as follows on <a href="https://caniuse.com/#search=position%3A%20sticky">caniuse</a>:</p> <blockquote> <p>Keeps elements positioned as &quot;fixed&quot; or &quot;relative&quot; depending on how it appears in the viewport. As a result, the element is &quot;stuck&quot; when necessary while scrolling.</p> </blockquote> <p>The other important note from caniuse data is that you will need to offer it prefixed for the best support. Our demo will fallback to <code>position: fixed</code> which will achieve the main goal just a bit less gracefully.</p> <div class="heading-wrapper h2"> <h2 id="about-scroll-behavior-smooth">About <code>scroll-behavior: smooth</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#about-scroll-behavior-smooth" aria-labelledby="about-scroll-behavior-smooth"><span hidden="">#</span></a></div> <p>This is a very new property, and <a href="https://caniuse.com/#search=scroll-behavior">support is relatively low</a>. This exact definition requests that scrolling behavior, particularly upon selection of an anchor link, has a smoothly animated appearance versus the default, more jarring instant jump.</p> <p>Using it offers &quot;progressive enhancement&quot; meaning that it will be a better experience for those whose browsers support it, but will also work for browsers that don't.</p> <p>Surprisingly, Safari is behind on supporting this, but the other major browsers do.</p> <div class="heading-wrapper h2"> <h2 id="set-the-stage-basic-content-html">Set the Stage: Basic content HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#set-the-stage-basic-content-html" aria-labelledby="set-the-stage-basic-content-html"><span hidden="">#</span></a></div> <p>First, we need to setup some semantic markup for a basic content format:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</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>top<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- long form content here --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Back to Top link --></span> <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>back-to-top-wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>#top<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>back-to-top-link<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>Scroll to Top<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🔝<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span></code></pre> <p>We place our link after the <code>article</code>, but within <code>main</code>. It isn't specifically part of the article, and we also want it to be last in focus order.</p> <p>We also add <code>id=&quot;top&quot;</code> to the <code>&lt;header&gt;</code> and use that anchor as the <code>href</code> value for the back to top link. If you only wanted to scroll to the top of <code>&lt;main&gt;</code> you can move the id, or also attach it to an existing id near the top of your page.</p> <div class="heading-wrapper h2"> <h2 id="add-smooth-scrolling">Add smooth-scrolling</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#add-smooth-scrolling" aria-labelledby="add-smooth-scrolling"><span hidden="">#</span></a></div> <p>The first part of our objective is easy peasy and is accomplished by the following CSS rule:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Smooth scrolling IF user doesn't have a preference due to motion sensitivities */</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> no-preference<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">scroll-behavior</span><span class="token punctuation">:</span> smooth<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><em>h/t to @mhlut for pointing out that the original solution was not accounting for <code>prefers-reduced-motion</code></em></p> <p>Previously, I had this <a href="https://css-tricks.com/snippets/jquery/smooth-scrolling/">CSS-Tricks article</a> bookmarked to accomplish this with jQuery and vanilla JS. The article has been around a while, and kudos to that team for continually updating articles like that when new methods are available 👍</p> <p>I have found some oddities, such as when you visit a page that includes an <code>#anchor</code> in the URL it still performs the smooth scroll which may not actually be desirable for your scenario.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="style-the-back-to-top-link">Style the &quot;Back to Top&quot; link</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#style-the-back-to-top-link" aria-labelledby="style-the-back-to-top-link"><span hidden="">#</span></a></div> <p>Before we make it work, let's apply some basic styling to the link. For fun, I used an emoji but you can swap for an SVG icon for more control over styling.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.back-to-top-link</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #d6e3f0<span class="token punctuation">;</span> <span class="token comment">/* emoji don't behave like regular fonts so this helped position it correctly */</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This gives us a very basic round button. In the full Codepen, I've added additional &quot;pretty&quot; styles and <code>:hover</code> and <code>:focus</code> styling, but those aren't essential.</p> <p>Next, you may wonder why we added a wrapper for this link. The reason is we need to use it to basically create a &quot;scroll track&quot; for the link to live within.</p> <p>The way <code>position: sticky</code> is designed, it picks up the element from where it's positioned in the DOM. Then, it respects a &quot;top&quot; value to place it relative to the viewport. However, we placed the link at the end of the document, so it would essentially never be picked up without some assistance.</p> <p>We will use the wrapper along with <code>position: absolute</code> to alter the link's position to visually higher up on the page. Thankfully, the browser uses this visually adjusted position - aka when it enters the viewport - to calculate when to &quot;stick&quot; the link.</p> <p>So, our wrapper styles look like this:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">/* How far of a scroll travel within &lt;main> prior to the link appearing */</span> <span class="token property"><span class="token variable">$scrollLength</span></span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token selector">.back-to-top-wrapper </span><span class="token punctuation">{</span> <span class="token comment">// uncomment to visualize "track"</span> <span class="token comment">// outline: 1px solid red;</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token variable">$scrollLength</span><span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token comment">// Optional, extends the final link into the</span> <span class="token comment">// footer at the bottom of the page</span> <span class="token comment">// Set to `0` to stop at the end of `main`</span> <span class="token property">bottom</span><span class="token punctuation">:</span> -5em<span class="token punctuation">;</span> <span class="token comment">// Required for best support in browsers not supporting `sticky`</span> <span class="token property">width</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span> <span class="token comment">// Disable interaction with this element</span> <span class="token property">pointer-events</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The last definition may also be new to you: <code>pointer-events: none</code>. This essentially lets interaction events like clicks &quot;fall through&quot; this element so that it doesn't block the rest of the document. We wouldn't want this wrapper to accidentally block links in the content, for example.</p> <p>With this in place, we now see the link overlapping the content a little bit below the initial viewport content. Let's add some styling to <code>&lt;main&gt;</code> to prevent this overlap, and also add <code>position: relative</code> which is necessary for best results given the absolute link wrapper:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">main </span><span class="token punctuation">{</span> <span class="token comment">// leave room for the "scroll track"</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 3rem<span class="token punctuation">;</span> <span class="token comment">// required to make sure the `absolute` positioning of</span> <span class="token comment">// the anchor wrapper is indeed `relative` to this element vs. the body</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 50rem<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 2rem auto<span class="token punctuation">;</span> <span class="token comment">// Optional, clears margin if last element is a block item</span> <span class="token selector">*:last-child </span><span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>All that's left is to revisit the link to add the necessary styles for the positioning to full work:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.back-to-top-link</span> <span class="token punctuation">{</span> // `fixed` is fallback when `sticky` not supported <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> // preferred positioning<span class="token punctuation">,</span> requires prefixing for most support<span class="token punctuation">,</span> and not supported on Safari // <span class="token atrule"><span class="token rule">@link</span> <span class="token property">https</span><span class="token punctuation">:</span>//caniuse.com/#search=position%3A%20sticky <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span></span> // reinstate clicks <span class="token property">pointer-events</span><span class="token punctuation">:</span> all<span class="token punctuation">;</span> // achieves desired positioning within the viewport // relative to the top of the viewport once `sticky` takes over<span class="token punctuation">,</span> or always if `fixed` fallback is used <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vh - 5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> // ... styles written earlier <span class="token punctuation">}</span></code></pre> <p>The <code>fixed</code> fallback means that browsers that don't support <code>sticky</code> positioning will always show the link. When <code>sticky</code> is supported, the fully desirable effect happens which is the link will not appear until the <code>$scrollLength</code> is passed. With <code>sticky</code> position, once the top of the wrapper is in the viewport, the link is &quot;stuck&quot; and scrolls with the user.</p> <p>Notice we also reinstated <code>pointer-events</code> with the <code>all</code> value so that interaction with the link actually work.</p> <p>And here's the final result - view in non-Safari for best results.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="OJyyqWR" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="known-issues">Known Issues</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#known-issues" aria-labelledby="known-issues"><span hidden="">#</span></a></div> <p>If you have short-form content, this doesn't fail very gracefully on a shorter device viewport. You may think - as did I - to use <code>overflow: hidden</code>. But that, unfortunately, prevents <code>position: sticky</code> from working entirely ☹️ So in a &quot;real world&quot; scenario, you may have to offer an option to toggle this behavior per article, or perform a calculation to determine if an article meets a length requirement before injecting it in the template.</p> <p>Drop a comment if you know of or find any other &quot;gotchas&quot; with these methods!</p> CSS-Only Full-Width Responsive Images 2 Ways 2020-04-14T00:00:00Z https://moderncss.dev/css-only-full-width-responsive-images-2-ways/ <p>In the not to distant past when jQuery was King of the Mountain and CSS3 was still worth being designated as such, the most popular tool for responsive background images was the <a href="https://www.jquery-backstretch.com/">Backstretch jQuery plugin</a>.</p> <p>I used this plugin on ~30 sites prior to the following property becoming more supported (aka IE &lt; 9 dropping in total market share):</p> <pre class="language-css"><code class="language-css"><span class="token property">background-size</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></code></pre> <p>According to <a href="https://caniuse.com/#feat=mdn-css_properties_background-size_contain_and_cover">caniuse.com</a>, this property and value have been well supported for over 9 years! But websites that are intertwined with using Backstretch or another homegrown solution may not yet have updated.</p> <p>The alternative method makes use of the standard <code>img</code> tag, and uses the magic of:</p> <pre class="language-css"><code class="language-css"><span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></code></pre> <p>Let's look at how to use each solution, and learn when to select one over the other.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="using-background-size-cover">Using <code>background-size: cover</code></h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#using-background-size-cover" aria-labelledby="using-background-size-cover"><span hidden="">#</span></a></div> <p>A decade of my background was creating highly customized WordPress themes and plugins for enterprise websites. So using the example of templated cards, here's how you might set up using the <code>background-size: cover</code> solution.</p> <p>First, the HTML, where the image is inserted into the <code>style</code> attribute as a <code>background-image</code>. An <code>aria-label</code> is encouraged to take the place of the <code>alt</code> attribute that would normally be present on a regular <code>img</code> tag.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</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>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>card__img<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>Preview of Whizzbang Widget<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://placeimg.com/320/240/tech<span class="token punctuation">)</span></span></span><span class="token punctuation">"</span></span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>card__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Whizzbang Widget SuperDeluxe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Liquorice candy macaroon soufflé jelly cake. Candy canes ice cream biscuit marzipan. Macaroon pie sesame snaps jelly-o. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Add to Cart<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>The relevant corresponding CSS would be the following, where <code>padding-bottom</code> is one weird trick that is used to set a 16:9 ratio on the div containing the image:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__img</span> <span class="token punctuation">{</span> <span class="token property">background-size</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">background-position</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> // 16<span class="token punctuation">:</span>9 ratio <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 62.5%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's this solution altogether:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvvVeo" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="using-object-fit-cover">Using <code>object-fit: cover</code></h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#using-object-fit-cover" aria-labelledby="using-object-fit-cover"><span hidden="">#</span></a></div> <p>This solution is a newer player, and is not available to you if you need to support IE &lt; Edge 16, according to <a href="https://caniuse.com/#search=object-fit">caniuse data</a> without a polyfill.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>This style is placed directly on the <code>img</code> tag, so we update our card HTML to the following, swapping the <code>aria-label</code> to <code>alt</code>:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</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>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</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>card__img<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Preview of Whizzbang Widget<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://placeimg.com/320/240/tech<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <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>card__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Whizzbang Widget SuperDeluxe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Liquorice candy macaroon soufflé jelly cake. Candy canes ice cream biscuit marzipan. Macaroon pie sesame snaps jelly-o. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Add to Cart<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>Then our updated CSS swaps to using the <code>height</code> property to constrain the image so that any size image conforms to the constrained ratio. If the inherent size of the image is greater than the constrained image size, the <code>object-fit</code> property takes over and by default centers the image within the bounds created by the card container + the <code>height</code> definition:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__img</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 30vh<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here's the result:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvvVqa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="when-to-use-each-solution">When to Use Each Solution</h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#when-to-use-each-solution" aria-labelledby="when-to-use-each-solution"><span hidden="">#</span></a></div> <p>If you have to support older versions of IE, then without a polyfill you are limited to the <code>background-size</code> solution (it pains me to say this in 2020, but this is a possibility particularly for enterprise and education industries).</p> <p>Both solutions enable a full-width responsive image based on a width:height ratio you control.</p> <p><strong>Choose <code>background-size</code> if</strong>:</p> <ul> <li>applying to a container expected to hold additional content, ex. a website header background</li> <li>to apply additional effect styles via pseudo elements which are not available to the <code>img</code> element</li> <li>to more gracefully apply a uniform size of image</li> <li>the image is purely decorative and the inherent <code>img</code> semantics are not needed</li> </ul> <p><strong>Choose <code>object-fit</code> if</strong>:</p> <ul> <li>using a standard <code>img</code> is best for your context in order to maintain all semantics provided by an image</li> </ul> Equal Height Elements: Flexbox vs. Grid 2020-04-09T00:00:00Z https://moderncss.dev/equal-height-elements-flexbox-vs-grid/ <p>Once upon a time (approximately 2013), I wrote a jQuery plugin to calculate equal height columns. It ensured that the very specific scenario of a row with three columns would keep the content boxes equal height no matter the length of the content they contained. The dominant layout method at the time - floats - did not handle this problem.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-solution">Flexbox Solution</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#flexbox-solution" aria-labelledby="flexbox-solution"><span hidden="">#</span></a></div> <p>When flexbox arrived on the scene, this became possible with:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.flexbox</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Amazing! By default, direct children line up in a row and have a &quot;stretch&quot; applied so they are equal height 🙌</p> <p>But then you add two <code>.column</code> divs as children and... the contents of the columns appear unequal again 😔</p> <p>The fix is:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.flexbox</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token selector">// Ensure content elements fill up the .column .element</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Now the columns will appear equal height and grow with the content of <code>.element</code>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="grid-solution">Grid Solution</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#grid-solution" aria-labelledby="grid-solution"><span hidden="">#</span></a></div> <p>With grid, we encounter similar behavior:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> // Essentially switch the default axis <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Similar to flexbox, direct children will be equal height, but their children need the height definition added just like in the flexbox solution:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token selector">// Ensure content elements fill up the .column .element</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Here's a demo of both solutions, as well as additional demos for defining a set amount of columns per row as described below:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="BaoamwO" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which is Better?</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>For purely solving for equal height elements, the advantage of flexbox is the default axis immediately enables side-by-side columns, whereas grid needs to be explicitly set. However, elements will not inherently be equal-width as well (which may be an advantage depending on type of content, for example navigation links).</p> <p>The advantage of grid is inherently equal-width elements if that is desirable. An additional advantage is when you don't want auto-flow but instead want to define a set max number of columns per &quot;row&quot;. In this case, grid layout easily handles the math to distribute the columns vs. a flexbox solution requiring defining the calculation to restrict the number of columns.</p> <p>Updating our <code>.grid</code> solution to handle for defining a max number of 3 <code>.column</code> per row is as simple as:</p> <pre class="language-css"><code class="language-css"><span class="token selector">&amp;.col-3</span> <span class="token punctuation">{</span> <span class="token property">gap</span><span class="token punctuation">:</span> $col_gap<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Whereas one (very basic) option for flexbox would be:</p> <pre class="language-css"><code class="language-css">$<span class="token property">col_gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token selector">.flexbox.col-3</span> <span class="token punctuation">{</span> // Explicitly needs to be defined to wrap // overflow items to the next virtual row <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token selector">.column</span> <span class="token punctuation">{</span> // <span class="token string">"hack"</span> for no gap property <span class="token property">margin</span><span class="token punctuation">:</span> $col_gap/2<span class="token punctuation">;</span> <span class="token selector">// define calculation for browser to use on the width max-width: calc((100% / 3) - #</span><span class="token punctuation">{</span>$col_gap<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>You would also need to consider how these solutions are handled responsively, but that's a bit out of scope of this article :)</p> Keep the Footer at the Bottom: Flexbox vs. Grid 2020-04-09T00:00:00Z https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/ <p>For many years, I constantly referred to <a href="https://matthewjamestaylor.com/bottom-footer">this article</a> by Matthew James Taylor for a method to keep a webpage footer at the bottom of the page regardless of the main content length. This method relied on setting an explicit footer height, which is not scalable but a very good solution BF (Before Flexbox).</p> <p>If you mostly deal with SPA development, you may be confused about why this problem is still around, but it's still a possibility to find your footer floating up for:</p> <ul> <li>login pages</li> <li>blog/news articles (with no ads...)</li> <li>interstitial pages of a flow like confirming actions</li> <li>product listing pages</li> <li>calendar event details</li> </ul> <p>There are two ways to handle this with modern CSS: flexbox and grid.</p> <p>Here's the demo, defaulted to the flexbox method. If you open the full Codepen, you can swap the <code>$method</code> variable to <code>grid</code> to view that alternative.</p> <p>Read on past the demo to learn about each method.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="abvboxz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-method">Flexbox Method</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#flexbox-method" aria-labelledby="flexbox-method"><span hidden="">#</span></a></div> <p>This method is accomplished by defining:</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">footer</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Optional */</span> <span class="token selector">main</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token comment">/* or: align-self: center */</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 80ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="how-it-works">How it Works</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#how-it-works" aria-labelledby="how-it-works"><span hidden="">#</span></a></div> <p>First, we ensure the <code>body</code> element will stretch to at least the full height of the screen with <code>min-height: 100vh</code>. This will not trigger overflow if content is short (exception: <a href="https://css-tricks.com/some-things-you-oughta-know-when-working-with-viewport-units/">certain mobile browsers</a>) and it will allow content to continue stretching the height as needed.</p> <p>Setting <code>flex-direction: column</code> keeps the behavior of normal document flow in terms of retaining stacked block-elements (which assumes direct children of <code>body</code> are all indeed block elements).</p> <p>The advantage of flexbox is in leveraging the <code>margin: auto</code> behavior. This one weird trick will cause the margin to fill any space between the item it is set on and its nearest sibling in the corresponding direction. Setting <code>margin-top: auto</code> effectively pushes the footer to the bottom of the screen.</p> <div class="heading-wrapper h3"> <h3 id="gotcha">Gotcha</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#gotcha" aria-labelledby="gotcha"><span hidden="">#</span></a></div> <p>In the demo, I've added an <code>outline</code> to <code>main</code> to demonstrate that in the flexbox method the <code>main</code> element doesn't fill the height. Which is why we have to use the <code>margin-top: auto</code> trick. This is not likely to matter to you, but if it does, see the grid method which stretches the <code>main</code> to fill the available space.</p> <div class="heading-wrapper h2"> <h2 id="grid-method">Grid Method</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#grid-method" aria-labelledby="grid-method"><span hidden="">#</span></a></div> <p>This method is achieved by setting:</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> auto 1fr auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Optional */</span> <span class="token selector">main</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 80ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="how-it-works-1">How it Works</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#how-it-works-1" aria-labelledby="how-it-works-1"><span hidden="">#</span></a></div> <p>We retain the <code>min-height: 100vh</code> for this method, but we then use of <code>grid-template-rows</code> to space things correctly.</p> <p>This method's weird trick is using the special grid unit <code>fr</code>. The <code>fr</code> means &quot;fraction&quot; and using it requests that the browser computes the available &quot;fraction&quot; of space that is left to distribute to that column or row. In this case, it fills all available space between the header and footer, which also solves the &quot;gotcha&quot; from the flexbox method.</p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which is Better?</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>After seeing grid, you may have a moment of thinking it's clearly superior. However, if you add more elements between the header and footer you need to update your template (or ensure there's always a wrapping element such as a <code>div</code> to not affect any nested semantics/hierarchy).</p> <p>On the other hand, the flexbox method is usable across various templates with multiple block elements in the midsection - for example, a series of <code>&lt;article&gt;</code> elements instead of a single <code>&lt;main&gt;</code> for an archive page.</p> <p>So as with all techniques, it depends on the project :) But we can all agree it's amazing to have these modern CSS layout methods!</p>
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Modern CSS Solutions</title>
<subtitle>A series examining modern CSS solutions to problems Stephanie Eckles (@5t3ph), a seasoned frontend developer, has been solving for 15+ years.</subtitle>
<link href="https://moderncss.dev/feed/" rel="self"/>
<link href="https://moderncss.dev"/>
<updated>2024-07-19T00:00:00Z</updated>
<id>https://moderncss.dev</id>
<author>
<name>Stephanie Eckles</name>
</author>
<entry>
<title>Providing Type Definitions for CSS with @property</title>
<link href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/"/>
<updated>2024-07-19T00:00:00Z</updated>
<id>https://moderncss.dev/providing-type-definitions-for-css-with-at-property/</id>
<content type="html"><p>A cross-browser feature as of the release of Firefox 128 in July 2024 is a new at-rule - <code>@property</code> - which allows defining types as well as inheritance and an initial value for your custom properties.</p> <p>We'll learn when and why traditional fallback values can fail, and how <code>@property</code> features allow us to write safer, more resilient CSS custom property definitions.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="standard-use-of-custom-properties">Standard Use of Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#standard-use-of-custom-properties" aria-labelledby="standard-use-of-custom-properties"><span hidden="">#</span></a></div> <p>Custom properties - aka &quot;CSS variables&quot; - are useful because they allow creating references to values similar to variables in other programming languages.</p> <p>Consider the following scenario which creates and assigns the <code>--color-blue</code> property, which then is implemented as a class and applied to a paragraph.</p> <details open=""> <summary>CSS for "Standard use case for custom properties"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-270 { --color-blue: blue; padding: 0.5rem; } .color-blue-270 { color: var(--color-blue); } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-270"> <p class="color-blue-270">I'm blue dabadee</p> </div> </div> </div> <p>The paragraph renders as blue. Excellent! Ship it.</p> <div class="heading-wrapper h2"> <h2 id="common-error-conditions-using-custom-properties">Common Error Conditions Using Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#common-error-conditions-using-custom-properties" aria-labelledby="common-error-conditions-using-custom-properties"><span hidden="">#</span></a></div> <p>Now, you and I both know that &quot;blue&quot; is a color. And it also may seem obvious that you would only apply the class that uses this color to text we intend to be <em>blue</em>.</p> <p>But, the real world isn't perfect, and sometimes the downstream consumers of our CSS end-up with a reason to re-define the value. Or perhaps they accidentally introduce a typo that impacts the original value.</p> <p>The outcome of either of these scenarios could be:</p> <ul> <li>the text ends up a color besides blue, as that author intended</li> <li>the text surprisingly renders as black</li> </ul> <p>If the rendered color is surprisingly black, it's likely that we've hit the unique scenario of <em>invalid at computed value time</em>.</p> <p>When the browser is assessing CSS rules and working out what value to apply to each property based on the cascade, inheritance, specificity and so forth, it will retain a custom property as the winning value as long as it understands the general way it's being used.</p> <p>In our <code>--color-blue</code> example, the browser definitely understands the <code>color</code> property, so it assumes all will be ok with the use of the variable as well.</p> <p>But, what happens if someone redefines <code>--color-blue</code> to an invalid color?</p> <details open=""> <summary>CSS for "Defining an invalid color"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-389 { --color-blue: blue; padding: 0.5rem; } .color-blue-389 { color: var(--color-blue); } .demo-389 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-389"> <p class="color-blue-389">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Uh oh - it's surprisingly rendering as black.</p> <div class="heading-wrapper h2"> <h2 id="why-traditional-fallbacks-can-fail">Why Traditional Fallbacks Can Fail</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#why-traditional-fallbacks-can-fail" aria-labelledby="why-traditional-fallbacks-can-fail"><span hidden="">#</span></a></div> <p>Ok, before we learn what that scary-sounding phrase means, let's take a look in DevTools and see if it gives us a clue about what's going on.</p> <p><img src="https://moderncss.dev/img/posts/35/devtools-notacolor.png" alt="Styles panel in DevTools shows .color-blue and the paragraph rule, with no error apparent" /></p> <p>That looks pretty normal, and doesn't seem to reveal that anything is wrong, making troubleshooting the error a lot trickier.</p> <p>You might know that custom properties allow a fallback value as second parameter, so perhaps that will help! Let's try.</p> <details open=""> <summary>CSS for "Attempt resolution with custom property fallback"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">,</span> blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-371 { --color-blue: blue; padding: 0.5rem; } .color-blue-371 { color: var(--color-blue, blue); } .demo-371 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-371"> <p class="color-blue-371">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Unfortunately, the text still renders as black.</p> <p>Ok, but our good friend the cascade exists, and back in the day we used to put things like vendor prefixed properties prior to the unprefixed ones. So, perhaps if we use a similar method and supply an extra <code>color</code> definition before the one that has the custom property it can fallback to that?</p> <details open=""> <summary>CSS for "Attempt resolution with extra color definition"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .demo-107 { --color-blue: blue; padding: 0.5rem; } .color-blue-107 { color: blue; color: var(--color-blue); } .demo-107 p { --color-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-107"> <p class="color-blue-107">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Bummer, we don't seem to be making progress towards preventing this issue.</p> <p>This is because of the (slightly scary sounding) scenario <em>invalid at computed value time</em>.</p> <p>Although the browser has kept our definition that expects a custom property value, it's not until later that the browser tries to actually compute that value.</p> <p>In this case, it looks at both the <code>.color-blue</code> class and the value provided for the <code>p</code> element rule and attempts to apply the computed value of <code>notacolor</code>. At this stage, it has discarded the alternate value of <code>blue</code> originally provided by the class. Consequently, since <code>notacolor</code> is in fact not a color and therefore <em>invalid</em>, the best it can do is use either:</p> <ul> <li>an <em>inherited value</em> if the property is allowed to inherit and an ancestor has provided a value; or</li> <li>the <em>initial value</em> as defined in the CSS spec</li> </ul> <p>While <code>color</code> is an inheritable property, we haven't defined it on any ancestors, so the rendered color of <code>black</code> is due to the <em>initial</em> value.</p> <blockquote> <p>Refer to this earlier Modern CSS article about <a href="https://moderncss.dev/how-custom-property-values-are-computed/">how custom property values are computed</a> and learn more deeply about the condition of invalid at computed value time.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="defining-types-for-safer-css">Defining Types for Safer CSS</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#defining-types-for-safer-css" aria-labelledby="defining-types-for-safer-css"><span hidden="">#</span></a></div> <p>It's time to introduce <code>@property</code> to help solve this issue of what you may perceive as a surprising rendered value.</p> <p>The critical features <code>@property</code> provides are:</p> <ul> <li>defining acceptable types for specific custom properties</li> <li>enabling or disabling inheritance</li> <li>providing an initial value as a failsafe for invalid or undefined values</li> </ul> <p>This at-rule is defined on a per-custom property basis, meaning a unique definition is needed for each property for which you want to leverage these benefits.</p> <p>It is <em>not</em> a requirement, and you can certainly continue using custom properties without ever bringing in <code>@property</code>.</p> <blockquote> <p>Please note that at time of writing, <code>@property</code> is very newly cross-browser and I would advise you to consider it a progressive enhancement to benefit users in supporting browsers.</p> </blockquote> <p>Let's apply it to our blue dilemma and see how it fixes the issue of the otherwise invalid color supplied in the element rule.</p> <details open=""> <summary>CSS for "Apply @property to --color-blue"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-blue</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Prior rules also apply */</span> </code></pre> </details> <style> @property --c-blue { syntax: "<color>"; inherits: true; initial-value: blue; } .demo-672 { --c-blue: blue; padding: 0.5rem; } .color-blue-672 { color: var(--c-blue); } .demo-672 p { --c-blue: notacolor; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-672"> <p class="color-blue-672">I'm blue dabadee (maybe)</p> </div> </div> </div> <p>Success, our text is still blue despite the invalid definition!</p> <p>Additionally, DevTools is now helpful again:</p> <p><img src="https://moderncss.dev/img/posts/35/devtools-with-at-property.png" alt="DevTools displays the invalid value within the paragraph rule as crossed out and with an error icon, and also provides a hover overlay for the --color-blue custom property with the full definition provided in @property" /></p> <p>We can observe both that the invalid value is clearly an error, and we also are provided the full definition of the custom property via the hover overlay.</p> <div class="heading-wrapper h2"> <h2 id="providing-types-via-syntax">Providing Types via <code>syntax</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#providing-types-via-syntax" aria-labelledby="providing-types-via-syntax"><span hidden="">#</span></a></div> <p>Why would we need types for custom properties? Here are a few reasons:</p> <ul> <li>types help verify what makes a valid vs. invalid value</li> <li>without types, custom properties are very open-ended and can take nearly any value, including a blank space</li> <li>lack of types prevents browser DevTools from providing the optimal level of detail about which value is in use for a custom property</li> </ul> <p>In our <code>@property</code> definition, the <code>syntax</code> descriptor enables providing the allowed types for the custom property. We used <code>&quot;&lt;color&gt;&quot;</code>, but other types include:</p> <ul> <li><code>&quot;&lt;length&gt;&quot;</code> - numbers with units attached, ex. <code>4px</code> or <code>3vw</code></li> <li><code>&quot;&lt;integer&gt;&quot;</code> - decimal units 0 through 9 (aka &quot;whole numbers&quot;)</li> <li><code>&quot;&lt;number&gt;&quot;</code> - numbers which may have a fraction, ex. <code>1.25</code></li> <li><code>&quot;&lt;percentage&gt;&quot;</code> - numbers with a percentage sign attached, ex. <code>24%</code></li> <li><code>&quot;&lt;length-percentage&gt;&quot;</code> - accepts valid <code>&lt;length&gt;</code> or <code>&lt;percentage&gt;</code> values</li> </ul> <p>A special case is <code>&quot;*&quot;</code> which stands for &quot;universal syntax&quot; and enables accepting any value, similar to the default behavior. This means you skip the typing benefit, but perhaps want the inheritance and/or initial value control.</p> <p>These types and more are listed for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax">the syntax descriptor on MDN</a>.</p> <p>The type applies to the <em>computed value</em> of the custom property, so the <code>&quot;&lt;color&gt;&quot;</code> type would be happy with both <code>blue</code> as well as <code>light-dark(blue, cyan)</code> (although only one of those is accepted into the <code>initial-value</code> as we will soon learn).</p> <div class="heading-wrapper h3"> <h3 id="stronger-typing-with-lists">Stronger Typing With Lists</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#stronger-typing-with-lists" aria-labelledby="stronger-typing-with-lists"><span hidden="">#</span></a></div> <p>Let's say we want to provide a little flexibility for our <code>--color-blue</code> custom property.</p> <p>We can use a list to provide valid options. Anything other than these <em>exact</em> values would be considered invalid, and use the <code>initial-value</code> instead (if inheritance didn't apply). These are called &quot;custom idents&quot;, are case sensitive, and can be any value.</p> <details open=""> <summary>CSS for "Defining a list within syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-blue</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"blue | cyan | dodgerblue"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-blue</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-blue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.demo p</span> <span class="token punctuation">{</span> <span class="token property">--color-blue</span><span class="token punctuation">:</span> dodgerblue<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --c2-blue { syntax: "blue | cyan | dodgerblue"; inherits: true; initial-value: blue; } .demo-396 { padding: 0.5rem; } .color-blue-396 { color: var(--c2-blue); } .demo-396 p { --c2-blue: dodgerblue; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-396"> <p class="color-blue-396">I'm blue dabadee (maybe)</p> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="typing-for-mixed-values">Typing for Mixed Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-mixed-values" aria-labelledby="typing-for-mixed-values"><span hidden="">#</span></a></div> <p>The pipe character (<code>|</code>) used in the previous list indicates an &quot;or&quot; condition. While we used explicit color names, it can also be used to say &quot;any of these syntax types are valid.&quot;</p> <pre class="language-css"><code class="language-css"><span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color> | &lt;length>"</span><span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="typing-for-multiple-values">Typing for Multiple Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-multiple-values" aria-labelledby="typing-for-multiple-values"><span hidden="">#</span></a></div> <p>So far, we've only typed custom properties that expect a <em>single</em> value.</p> <p>Two additional cases can be covered with an additional &quot;multiplier&quot; character, which should immediately follow the syntax component name.</p> <ul> <li>Use <code>+</code> to support a space-separated list, ex. <code>&quot;&lt;length&gt;+&quot;</code></li> <li>Use <code>#</code> to support a comma-separated list, ex. <code>&quot;&lt;length&gt;#&quot;</code></li> </ul> <p>This can be useful for properties that allow multiple definitions, such as <code>background-image</code>.</p> <details open=""> <summary>CSS for "Support multiple values for syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --bg-gradient</span><span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;image>#"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>to right<span class="token punctuation">,</span> blue 10px 12px<span class="token punctuation">,</span> transparent 12px 22px<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span>to bottom<span class="token punctuation">,</span> blue 10px 12px<span class="token punctuation">,</span> transparent 12px 22px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 5rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --bg-gradient{ syntax: "<image>#"; inherits: false; initial-value: repeating-linear-gradient(to right, blue 10px 12px, transparent 12px 22px), repeating-linear-gradient(to bottom, blue 10px 12px, transparent 12px 22px); } .box-310 { background-image: var(--bg-gradient); inline-size: 5rem; aspect-ratio: 1; border-radius: 4px; border: 1px solid; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-310"></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="typing-for-multi-part-mixed-values">Typing for Multi-Part Mixed Values</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#typing-for-multi-part-mixed-values" aria-labelledby="typing-for-multi-part-mixed-values"><span hidden="">#</span></a></div> <p>Some properties accept mixed types to develop the full value, such as <code>box-shadow</code> which has potential types of <code>inset</code>, a series of <code>&lt;length&gt;</code> values, and a <code>&lt;color&gt;</code>.</p> <p>Presently, it's not possible to type this in a single <code>@property</code> definition, although you may attempt to try something like <code>&quot;&lt;length&gt;+ &lt;color&gt;&quot;</code>. However, this effectively invalidates the <code>@property</code> definition itself.</p> <p>One alternative is to break up the custom property definitions so that we can allow a series of lengths, and then allow a color. While slightly more cumbersome, this allows us to still get the benefit of typing which relieves the potential errors we covered earlier.</p> <details open=""> <summary>CSS for "Support multi-part mixed values for syntax"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-shadow-length</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length>+"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0px 0px 8px 2px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --box-shadow-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 75%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-shadow-length<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-shadow-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 5rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --box-shadow-length { syntax: "<length>+"; inherits: false; initial-value: 0px 0px 8px 2px; } @property --box-shadow-color { syntax: "<color>"; inherits: false; initial-value: hsl(0 0% 75%); } .box-707 { box-shadow: var(--box-shadow-length) var(--box-shadow-color); inline-size: 5rem; aspect-ratio: 1; border-radius: 4px; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-707"></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="allowing-any-type">Allowing Any Type</h3> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#allowing-any-type" aria-labelledby="allowing-any-type"><span hidden="">#</span></a></div> <p>If you're less concerned about the &quot;type&quot; of a property for something like <code>box-shadow</code> and care more about inheritance or the initial value, you can instead use the universal syntax definition to allow any value. This negates the problem we just mitigated by splitting up the parts.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-shadow</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"*"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0px 0px 8px 2px <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 75%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Because the universal syntax accepts any value, an additional &quot;multiplier&quot; is not needed.</p> <p><em>Note</em>: The <code>initial-value</code> is still required to be <em>computationally independent</em> as we'll learn about soon under <a href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#limitations-of-initial-value">limitations of initial-value</a>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="modifying-inheritance">Modifying Inheritance</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#modifying-inheritance" aria-labelledby="modifying-inheritance"><span hidden="">#</span></a></div> <p><a href="https://web.dev/learn/css/inheritance#which_properties_are_inherited_by_default">A subset of CSS properties are inheritable</a>, such as <code>color</code>. The <code>inherits</code> descriptor for your <code>@property</code> registration allows you to control that behavior for your custom property.</p> <p>If <code>true</code>, the computed value can look to an ancestor for its value if the property is not explicitly set, and if a value isn't found it will use the initial value.</p> <p>If <code>false</code>, the computed value will use the initial value if the property is not explicitly set for the element, such as via a class or element rule.</p> <p>In this demonstration, the <code>--box-bg</code> has been set to <code>inherits: false</code>, and only the outer box has an explicit definition via the applied class. The inner box uses the initial value since inheritance is not allowed.</p> <details open=""> <summary>CSS for "Result of setting inherits: false"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --box-bg</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> cyan<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--box-bg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.outer-box</span> <span class="token punctuation">{</span> <span class="token property">--box-bg</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --box-bg { syntax: "<color>"; inherits: false; initial-value: cyan; } .box-215 { background-color: var(--box-bg); aspect-ratio: 1; border-radius: 4px; padding: 1.5rem; } .outer-box-215 { --box-bg: purple; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="box-215 outer-box-215"> <div class="box-215"></div> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="valid-use-of-initial-value">Valid Use of <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#valid-use-of-initial-value" aria-labelledby="valid-use-of-initial-value"><span hidden="">#</span></a></div> <p>Unless your syntax is open to any value using the universal syntax definition - <code>&quot;*&quot;</code> - then it is required to set an <code>initial-value</code> to gain the benefits of registering a custom property.</p> <p>As we've already experienced, use of <code>initial-value</code> was critical in preventing the condition of a completely broken render due to <em>invalid at computed value time</em>. Here are some other benefits of using <code>@property</code> with an <code>initial-value</code>.</p> <p>When building design systems or UI libraries, it's important to ensure your custom properties are robust and reliable. Providing an <code>initial-value</code> can help prevent a broken experience. Plus, typing properties also meshes nicely with keeping the intent of design tokens which may be expressed as custom properties.</p> <p>Dynamic computation scenarios such as the use of <code>clamp()</code> have the potential to include an invalid value, whether through an error or from the browser not supporting something within the function. Having a fallback via <code>initial-value</code> ensures that your design remains functional. This fallback behavior is a safeguard for unsupported features as well, though that can be limited by whether the <code>@property</code> rule is supported in the browser being used.</p> <blockquote> <p>Review additional ways to <a href="https://moderncss.dev/how-custom-property-values-are-computed/#preventing-invalid-at-computed-value-time">prevent invalid at computed time</a> that may be more appropriate for your browser support matrix, especially for critical scenarios.</p> </blockquote> <p>Incorporating <code>@property</code> with <code>initial-value</code> not only enhances the reliability of your CSS but also opens the door to the possibility of better tooling around custom properties. We've previewed the behavior change in browser DevTools, but I'm hopeful for an expansion of tooling including IDE plugins.</p> <p>The added layer of security from using <code>@property</code> with <code>initial-value</code> helps maintain the intent of your design, even if it isn't perfect for every context.</p> <div class="heading-wrapper h2"> <h2 id="limitations-of-initial-value">Limitations of <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#limitations-of-initial-value" aria-labelledby="limitations-of-initial-value"><span hidden="">#</span></a></div> <p>The <code>initial-value</code> is subject to the <code>syntax</code> you define for <code>@property</code>. Beyond that, <code>syntax</code> itself doesn't support every possible value combination, which we previously covered. So, sometimes a little creativity is needed to get the benefit.</p> <p>Also, <code>initial-value</code> values must be <a href="https://www.w3.org/TR/css-properties-values-api-1/#the-registerproperty-function">what the spec calls <em>computationally independent</em></a>. Simplified, this means relative values like <code>em</code> or dynamic functions like <code>clamp()</code> or <code>light-dark()</code> are unfortunately not allowed. However, in these scenarios you can still set an acceptable initial value, and then use a relative or dynamic value when you <em>use</em> the custom property, such as in the <code>:root</code> assignment.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --heading-font-size</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--heading-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> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This limitation on relative units or dynamic functions also means other custom properties cannot be used in the <code>initial-value</code> assignment. The previous technique can still be used to mitigate this, where the preferred outcome is composed in the use of the property.</p> <p>Finally, custom properties registered via <code>@property</code> are still locked into the rules of regular properties, such as that they cannot be used to enable variables in media or container query at-rules. For example, <code>@media (min-width: var(--mq-md))</code> would still be invalid.</p> <div class="heading-wrapper h2"> <h2 id="unsupported-initial-value-can-crash-the-page">Unsupported <code>initial-value</code> Can Crash the Page</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#unsupported-initial-value-can-crash-the-page" aria-labelledby="unsupported-initial-value-can-crash-the-page"><span hidden="">#</span></a></div> <p>As of time of writing, using a property or function value that a browser may not support as part of the <code>initial-value</code> definition can cause the entire page to crash!</p> <p>Fortunately, we can use <code>@supports</code> to test for ultra-modern properties or features before we try to use them as the <code>initial-value</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span>[property|feature]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Feature is supported, use for initial-value */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span>[property|feature]<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Feature unsupported, use alternate for initial-value */</span> <span class="token punctuation">}</span></code></pre> <p>There may still be some surprises where <code>@supports</code> reports true, but testing will reveal a crash or other error (ex. <code>currentColor</code> used with <code>color-mix()</code> in Safari). Be sure to test your solutions cross-browser!</p> <p>Learn more about ways to <a href="https://moderncss.dev/testing-feature-support-for-modern-css/">test feature support for modern CSS</a>.</p> <div class="heading-wrapper h2"> <h2 id="exceptions-to-dynamic-limitations">Exceptions to Dynamic Limitations</h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#exceptions-to-dynamic-limitations" aria-labelledby="exceptions-to-dynamic-limitations"><span hidden="">#</span></a></div> <p>There are a few conditions which may feel like exceptions to the requirement of &quot;computationally independent&quot; values when used for the <code>initial-value</code>.</p> <p>First, <code>currentColor</code> is accepted. Unlike a relative value such as <code>em</code> which requires computing <code>font-size</code> of ancestors to compute itself, the value of <code>currentColor</code> can be computed without depending on context.</p> <details open=""> <summary>CSS for "Use of currentColor as initial-value"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --border-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> false<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 3px solid <span class="token function">var</span><span class="token punctuation">(</span>--border-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --border-color { syntax: "<color>"; inherits: false; initial-value: currentColor; } .demo-553 h2 { color: blue; border: 3px solid var(--border-color); padding: 1em; } .demo-553 code { color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="demo-553"> <h2>My border is set to <code>currentColor</code></h2> </div> </div> </div> <p>Second, use of <code>&quot;&lt;length-percentage&gt;&quot;</code> enables the use of <code>calc()</code>, which is mentioned in the spec. This allows a calculation that includes what is considered a global, computationally independent unit set even though we often use them for dynamic behavior. That is, the use of viewport units.</p> <p>For a scenario such as fluid type, this provides a better fallback that keeps the spirit of the intended outcome even though it's overall less ideal for most scenarios.</p> <details open=""> <summary>CSS for "Use of calc() with vi for initial-value"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --heading-font-size</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;length-percentage>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>18px + 1.5vi<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* In practice, define your ideal sizing function using `clamp()` via an assignment on `:root` */</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--heading-font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @property --heading-font-size { syntax: "<length-percentage>"; inherits: true; initial-value: calc(18px + 1.5vi); } .demo-996 h2 { font-size: var(--heading-font-size); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="demo-996"> <h2>Resize the window to see the fluid behavior</h2> </div> </div> </div> <p><em>Note</em>: While we typically recommend using <code>rem</code> for <code>font-size</code> definitions, it is considered a relative value and not accepted for use in <code>initial-value</code>, hence the use of <code>px</code> in the calculation.</p> <div class="heading-wrapper h2"> <h2 id="consequences-of-setting-initial-value">Consequences of Setting <code>initial-value</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#consequences-of-setting-initial-value" aria-labelledby="consequences-of-setting-initial-value"><span hidden="">#</span></a></div> <p>In some scenarios, registering a property without the universal syntax - which means an <code>initial-value</code> is required - has consequences, and limits the property's use.</p> <p>Some reasons for preferring optional component properties include:</p> <ul> <li>to use the regular custom property fallback method for your default value, especially if the fallback should be another custom property (ex. a design token)</li> <li>an <code>initial-value</code> may result in an unwanted default condition, particularly since it can't include another custom property</li> </ul> <p>A technique I love to use for flexible component styles is including an intentionally undefined custom property so that variants can efficiently be created just by updating the custom property value. Or, purposely using entirely undefined properties to make the base class more inclusive of various scenarios by treating custom properties like a component style API.</p> <p>For example, if I registered <code>--button-background</code> here as a color, it would never use the correct fallback when my intention was for the default variant to use the fallback.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.button</span> <span class="token punctuation">{</span> <span class="token comment">/* Use of initial-value would prevent ever using the fallback */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-background<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Intended to be undefined and therefore considered invalid until set */</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--button-border-radius<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button--secondary</span> <span class="token punctuation">{</span> <span class="token property">--button-background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-secondary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button--rounded</span> <span class="token punctuation">{</span> <span class="token property">--button-border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>If you also have these scenarios, you may consider using a mixed approach of typing your primitive properties - like <code>--color-primary</code> - but not the component-specific properties.</p> <div class="heading-wrapper h2"> <h2 id="considerations-for-using-property">Considerations For Using <code>@property</code></h2> <a class="anchor" href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/#considerations-for-using-property" aria-labelledby="considerations-for-using-property"><span hidden="">#</span></a></div> <p>While some of the demos in this article intentionally were set up to have the rendered output use <em>only</em> the <code>initial-value</code>, in practice it would be best to separately define the custom property. Again, this is presently a new feature, so without an additional definition such as in <code>:root</code> you risk not having a value at all if you swap to only relying on <code>initial-value</code>.</p> <p>You should also be aware that it is possible to register the same property multiple times, and that cascade rules mean the last one will win. This raises the potential for conflicts from accidental overrides. There isn't a way to &quot;scope&quot; the <code>@property</code> rule within a selector.</p> <p>However, use of cascade layers can modify this behavior since unlayered styles win over layered styles, which includes at-rules. Cascade layers might be a way to manage registration of <code>@property</code> rules if you assign a &quot;properties&quot; layer early on and commit to assigning all registrations to that layer.</p> <p>Custom properties can also be <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS/registerProperty_static">registered via JavaScript</a>. In fact, this was the original way to do it since this capability was originally coupled with the Houdini APIs. If a property is registered via JS, that definition is likely to win over the one in your stylesheets. That said, if your actual intent is to change a custom property value via JS, learn the more appropriate way to <a href="https://12daysofweb.dev/2021/css-custom-properties/#accessing-and-setting-custom-properties-with-javascript">access and set custom properties with JS</a>.</p> <p>Use of <code>@property</code> has the potential for strengthening container style queries, especially if you are registering properties to act as toggles or enums. In this example, the use of <code>@property</code> helps by typing our theme values, and ensures a fallback of &quot;light&quot;.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --theme</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"light | dark"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--theme</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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">--theme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><em>Learn more about this particular idea of <a href="https://thinkdobecreate.com/articles/simplified-dark-mode-with-style-queries/">using style queries for simplified dark mode</a></em>.</p> <p>Although it's a bit outside the scope of this article, another benefit of typing custom properties is that they become animatable. This is because the type turns the value into something CSS knows how to work with, vs. the mysterious open-ended value it would otherwise be. Here's a CodePen example of how registering a color custom property allows <a href="https://codepen.io/5t3ph/pen/LYgWQgL">animating a range of colors</a> for the background.</p> <hr /> <p>Use of <code>@property</code> enables writing safer CSS custom properties, which improves the reliability of your system design, and defends against errors that could impact user experience. A reminder that for now they are a progressive enhancement and should almost always be used in conjunction with an explicit definition of the property.</p> <p>Be sure to test to ensure your intended outcome of both the allowed syntax, and the result if the <code>initial-value</code> is used in the final render.</p> </content>
</entry>
<entry>
<title>12 Modern CSS One-Line Upgrades</title>
<link href="https://moderncss.dev/12-modern-css-one-line-upgrades/"/>
<updated>2024-01-19T00:00:00Z</updated>
<id>https://moderncss.dev/12-modern-css-one-line-upgrades/</id>
<content type="html"><p>Sometimes, improving your application CSS just takes a one-line upgrade or enhancement! Learn about 12 properties to start incorporating into your projects, and enjoy reducing technical debt, removing JavaScript, and scoring easy wins for user experience.</p> <p>Properties are explored for the following categories:</p> <ul> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-upgrades">Stable Upgrades</a></strong>: fix a hack or issue by replacing older techniques</li> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-enhancements">Stable Enhancements</a></strong>: provide an improved experience with well-supported modern properties</li> <li><strong><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/#progressive-enhancements">Progressive Enhancements</a></strong>: provide an upgraded experience when these properties are supported without causing harm in unsupporting browsers</li> </ul> <div class="heading-wrapper h2"> <h2 id="stable-upgrades"><strong>Stable Upgrades</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-upgrades" aria-labelledby="stable-upgrades"><span hidden="">#</span></a></div> <p>The following well-supported properties can help fix a hack or long-standing issue by replacing older techniques.</p> <div class="heading-wrapper h3"> <h3 id="aspect-ratio">aspect-ratio</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#aspect-ratio" aria-labelledby="aspect-ratio"><span hidden="">#</span></a></div> <p>Have you ever used the “<a href="https://css-tricks.com/aspect-ratio-boxes/">padding hack</a>” to force an aspect ratio such as 16:9 for video embeds? As of September 2021, the <code>aspect-ratio</code> property is stable in evergreen browsers and is the only property needed to define an aspect ratio.</p> <p>For an HD video, you can just use <code>aspect-ratio: 16/9</code>. For a perfect square, only <code>aspect-ratio: 1</code> is required since the implied second value is also <code>1 </code>.</p> <details open=""> <summary>CSS for "Basic use of aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.aspect-ratio-hd</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 16/9<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.aspect-ratio-square</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .aspect-ratio-hd-258 { aspect-ratio: 16/9; } .aspect-ratio-square-258 { aspect-ratio: 1; } :is(.aspect-ratio-hd-258, .aspect-ratio-square-258) { background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); display: grid; place-content: center; padding: 1em; } :is(.aspect-ratio-hd-258, .aspect-ratio-square-258):not(:first-child) { margin-top: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <p class="aspect-ratio-hd-258"> HD 16:9 </p> <p class="aspect-ratio-square-258"> Square 1:1 </p> </div> </div> <p>Of note, an applied <code>aspect-ratio</code> is forgiving and will allow content to take precedence. This means that when content would cause the element to exceed the ratio in at least one dimension, the element will still grow or change shape to accommodate the content. To prevent or control this behavior, you can add additional dimension properties, like <code>max-width</code>, which may be necessary to avoid expanding out of a flex or grid container.</p> <details open=""> <summary>CSS for "Forgiving aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token comment">/* Applied to the flexbox children which have a size constraint from their parent */</span> <span class="token selector">.aspect-ratio-square</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .aspect-ratio-square-88 { aspect-ratio: 1; background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); display: grid; align-items: center; padding: 1em; } .container-88 { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-start; margin-inline: auto; max-width: 65ch; } .container-88 > * { flex: 1 1 20ch; } </style> <div class="demo"> <div class="demo--content demo--place-center demo--padding"> <div class="container-88"> <p class="aspect-ratio-square-88"> Lorem, ipsum dolor sit amet consectetur adipisicing elit. Iste molestias maiores velit quaerat debitis incidunt delectus nulla, quibusdam fugit eos? Fugiat asperiores assumenda nulla corrupti, sit repellendus ducimus necessitatibus voluptates. </p> <p class="aspect-ratio-square-88"> Lorem, ipsum dolor sit amet consectetur adipisicing elit. </p> <p class="aspect-ratio-square-88"> Iste molestias maiores velit quaerat debitis incidunt delectus nulla, quibusdam fugit eos? </p> </div> </div> </div> <p>If you are hesitant to fully replace the padding hack and still want to provide some dimension guardrails, review the <a href="https://smolcss.dev/#smol-aspect-ratio-gallery">Smol Aspect Ratio Gallery</a> for a progressively enhanced solution to <code>aspect-ratio</code>.</p> <div class="heading-wrapper h3"> <h3 id="object-fit">object-fit</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#object-fit" aria-labelledby="object-fit"><span hidden="">#</span></a></div> <p>This is actually the oldest property in this list, but it solves an important issue and definitely fits the sentiment of a one-line upgrade.</p> <p>The use of <code>object-fit</code> causes an <code>img</code> or other <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element">replaced element</a> to act as the container for its contents and have the those contents adopt resizing behavior similar to <code>background-size</code>.</p> <p>While there are a few values available for <code>object-fit</code>, the following are the ones you’re most likely to use:</p> <ul> <li><code>cover</code> - the image resizes to <em>cover</em> the element, and maintains its aspect-ratio so that the content is not distorted</li> <li><code>scale-down</code> - the image resizes (if needed) <em>within</em> the element so that it is fully visible without being clipped and maintains its aspect-ratio, which may lead to extra space (”letterboxing”) around the image if the element has a different rendered aspect-ratio</li> </ul> <p>In either case, <code>object-fit</code> is an excellent property pairing with <code>aspect-ratio</code> to ensure images are not distorted when you apply a custom aspect ratio.</p> <details open=""> <summary>CSS for "Use of object-fit with aspect-ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.image</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token comment">/* Optional: constrain image size */</span> <span class="token property">max-block-size</span><span class="token punctuation">:</span> 250px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .image-373 { object-fit: cover; aspect-ratio: 1; /* Optional: constrain image size */ max-block-size: 250px; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <img src="https://moderncss.dev/img/posts/photo.jpg" alt="A closeup of a siamese cat peeking over an object only their head visible from the eyes up to their ears." class="image-373" /> </div> </div> <blockquote> <p>Review my <a href="https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit?af=2s65ms">explanation of object-fit</a> in this free egghead video lesson.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="margin-inline">margin-inline</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#margin-inline" aria-labelledby="margin-inline"><span hidden="">#</span></a></div> <p>One of many logical properties, <code>margin-inline</code> functions as a shorthand for setting the inline (left and right in horizontal writing modes) margin.</p> <p>The replacement here is simple:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Before */</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token comment">/* After */</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></code></pre> <p>Logical properties have been available for a couple of years and now have <a href="https://caniuse.com/css-logical-props">support upwards of 98%</a> (with occasional prefixing). Review this article from Ahmad Shadeed to learn more about <a href="https://ishadeed.com/article/css-logical-properties/">using logical properties</a> and their importance for sites with international audiences.</p> <div class="heading-wrapper h2"> <h2 id="stable-enhancements"><strong>Stable Enhancements</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#stable-enhancements" aria-labelledby="stable-enhancements"><span hidden="">#</span></a></div> <p>These well-supported modern CSS properties can provide an improved experience, and may also allow replacing older methods or even JavaScript-aided solutions. Fallback solutions are not likely to be needed, although this is dependent on your specific application considerations, and testing is always encouraged.</p> <div class="heading-wrapper h3"> <h3 id="text-underline-offset">text-underline-offset</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#text-underline-offset" aria-labelledby="text-underline-offset"><span hidden="">#</span></a></div> <p>The use of <code>text-underline-offset</code> allows you to control the distance between the text baseline and the underline. This property has become a part of my standard reset, applied as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">text-underline-offset</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can use this offset to clear descenders as well as (subjectively) improve legibility, particularly when links are grouped in close proximity, such as a bulleted list of links.</p> <p>This upgrade may replace older hacks like a border or pseudo-element, or even a gradient background, especially when used with its friends:</p> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color">text-decoration-color</a> to change the underline color</li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-thickness">text-decoration-thickness</a> to change the underline stroke thickness.</li> </ul> <div class="heading-wrapper h3"> <h3 id="outline-offset">outline-offset</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#outline-offset" aria-labelledby="outline-offset"><span hidden="">#</span></a></div> <p>Have you been using <code>box-shadow</code> or perhaps a pseudo-element to supply a custom outline when you wanted distance between the element and outline on focus?</p> <p>Good news! The long-available <code>outline-offset</code> property (<a href="https://caniuse.com/?search=outline-offset">as early as 2006</a>!) may be one you missed, and it enables pushing the outline away from the element with a positive value or pulling it into the element with a negative value.</p> <p>In the demo, the gray solid line is the element border, and the blue dashed line is the outline being positioned via <code>outline-offset</code>.</p> <details open=""> <summary>CSS for "Positive and negative outline-offset"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.outline-offset</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px dashed blue<span class="token punctuation">;</span> <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> .5em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-488 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; gap: 2rem; } .outline-offset-488 { outline: 2px dashed blue; outline-offset: var(--outline-offset, 0.5em); padding: 1em; border: 1px solid gray; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <div class="container-488"> <span class="outline-offset-488">Positive offset</span> <span class="outline-offset-488" style="--outline-offset: -0.5em;">Negative offset</span> </div> </div> </div> <p><strong>Reminder</strong>: outlines are <em>not</em> computed as part of the element’s box size, so increasing the distance will not increase the amount of space an element occupies. This is similar to how <code>box-shadow</code> is rendered without impacting the element size as well.</p> <blockquote> <p>Learn more about <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility">using outline-offset as an accessibility improvement</a> for focus visibility.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="scroll-margin-topbottom">scroll-margin-top/bottom</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#scroll-margin-topbottom" aria-labelledby="scroll-margin-topbottom"><span hidden="">#</span></a></div> <p>The <code>scroll-margin</code> set of properties (and corresponding <code>scroll-padding</code>) allows adding an offset to an element in the context of the scroll position. In other words, adding <code>scroll-padding-top</code> can increase scroll offset above the element but doesn’t affect its layout position within the document.</p> <p>Why is this useful? Well, it can alleviate issues caused by a sticky nav element covering content when an anchor link is activated. Using <code>scroll-margin-top</code> we can increase the space above the element when it is scrolled to via navigation to account for the space occupied by the sticky nav.</p> <p>I like to include a generic starting rule in my reset for any element with an <code>[id]</code> attribute given it has the potential to become an anchor link.</p> <pre class="language-css"><code class="language-css"><span class="token selector">[id]</span> <span class="token punctuation">{</span> <span class="token property">scroll-margin-top</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>An alternative selector is explored in the Modern CSS article on <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-reset-additions">component-based architecture</a> and is also in use on this site, as can be tested by using the links from the article table of contents sidebar.</p> <p>For a more robust solution when accounting for the overlap of sticky, fixed, or absolute positioned elements, you may want to use a custom property with a fallback value. Then, with the assistance of JavaScript, measure the real distance needed and <a href="https://12daysofweb.dev/2021/css-custom-properties/#accessing-and-setting-custom-properties-with-javascript">update the custom property value</a>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">[id]</span> <span class="token punctuation">{</span> <span class="token comment">/* Update --scroll-margin with JS if needed */</span> <span class="token property">scroll-margin-top</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--scroll-margin<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>I encourage you to also update this solution further and use the logical property equivalents: <code>scroll-padding-block-start</code> and <code>-block-end</code>.</p> <div class="heading-wrapper h3"> <h3 id="color-scheme">color-scheme</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#color-scheme" aria-labelledby="color-scheme"><span hidden="">#</span></a></div> <p>You may be familiar with the <code>prefers-color-scheme</code> media query to customize dark and light themes. The CSS property <code>color-scheme</code> is an opt-in to adapting browser UI elements including form controls, scrollbars, and CSS system colors. The adaptation asks the browser to render those items with either a <code>light</code> or <code>dark</code> scheme, and the property allows defining a preference order.</p> <p>If you’re enabling adapting your entire application, set the following on the <code>:root</code>, which says to preference a <code>dark</code> theme (or flip the order to preference a <code>light</code> theme).</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> dark light<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can also define <code>color-scheme</code> on individual elements, such as adjusting form controls within an element with a dark background for improved contrast.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.dark-background</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Learn from Sara Joy’s presentation about how to <a href="https://www.youtube.com/watch?v=Lye56NHGtLA">use color-scheme for easy dark mode</a>, and more about incorporating this feature.</p> <div class="heading-wrapper h3"> <h3 id="accent-color">accent-color</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#accent-color" aria-labelledby="accent-color"><span hidden="">#</span></a></div> <p>If you’ve ever wanted to change the color of checkboxes or radio buttons, you’ve been seeking <code>accent-color</code>. With this property, you can modify the <code>:checked</code> appearance of radio buttons and checkboxes and the filled-in state for both the progress element and range input. The browser’s default focus “halo” may also be adjusted if you do not have another override.</p> <details open=""> <summary>CSS for "Effect of using accent-color"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">accent-color</span><span class="token punctuation">:</span> mediumvioletred<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-129 { display: grid; gap: 1rem; accent-color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center demo--padding"> <div class="container-129"> <label> <input type="radio" checked="" /> Radio </label> <label> <input type="checkbox" checked="" /> Checkbox </label> <label> Range<br /> <input type="range" min="0" max="100" value="30" /> </label> <label> Progress<br /> <progress max="100" value="70">70%</progress> </label> </div> </div> </div> <p>Consider adding both <code>accent-color</code> and <code>color-scheme</code> to your baseline application styles for a quick win toward custom theme management.</p> <blockquote> <p>If you need more comprehensive custom styling for form controls, review the Modern CSS series beginning with <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="width-fit-content">width: fit-content</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#width-fit-content" aria-labelledby="width-fit-content"><span hidden="">#</span></a></div> <p>One of my favorite CSS hidden gems is the use of <code>fit-content</code> to “shrink wrap” an element to its contents.</p> <p>Whereas you may have used an inline display value such as <code>display: inline-block</code> to reduce an element’s width to the content size, an upgrade to <code>width: fit-content</code> will achieve the same effect. The advantage of <code>width: fit-content</code> is that it leaves the <code>display</code> value available, thereby not changing the position of the element in the layout unless you adjust that as well. The computed box size will adjust to the dimensions created by <code>fit-content</code>.</p> <details open=""> <summary>CSS for "Basic usage of fit-content"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fit-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fit-content-135 { width: fit-content; padding: 0.5em; border-radius: 0.25rem; background-color: hsl(260, 90%, 95%); border: 1px dashed hsl(260, 90%, 65%); } .regular-135 { background-color: lightgray; margin-top: 1em; padding: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="fit-content-135">Using fit-content</p> <p class="regular-135">Without the use of fit-content</p> </div> </div> <p>The <code>fit-content</code> value is one of several <a href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/">keywords that enable intrinsic sizing</a>.</p> <p>Consider the secondary upgrade for this technique to the logical property equivalent of <code>inline-size: fit-content</code>.</p> <div class="heading-wrapper h2"> <h2 id="progressive-enhancements"><strong>Progressive Enhancements</strong></h2> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#progressive-enhancements" aria-labelledby="progressive-enhancements"><span hidden="">#</span></a></div> <p>This last set of properties provides an upgraded experience when they are supported and can be used without fear of causing harm in unsupporting browsers. This means they do not need a fallback method even though they are more recent additions to modern CSS.</p> <div class="heading-wrapper h3"> <h3 id="overscroll-behavior">overscroll-behavior</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#overscroll-behavior" aria-labelledby="overscroll-behavior"><span hidden="">#</span></a></div> <p>The default behavior of contained scroll regions - areas with limited dimensions where overflow is allowed to be scrolled - is that when the scroll runs out in the element, the scroll interaction passes to the background page. This can be jarring at best, and frustrating at worst for your users.</p> <p>Use of <code>overscroll-behavior: contain</code> will isolate the scrolling to the contained region, preventing continuing the scroll by moving it to the parent page once the scroll boundary is reached. This is useful in contexts such as a sidebar of navigation links, which may have an independent scroll from the main page content, which may be a long article or documentation page.</p> <details open=""> <summary>CSS for "Basic usage of overscroll-behavior"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar, .article</span> <span class="token punctuation">{</span> <span class="token property">overscroll-behavior</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-736 { display: grid; grid-template-columns: fit-content(25ch) minmax(50%, 1fr); } .sidebar-736, .article-736 { display: grid; gap: 1em; padding: 1rem; height: max(40vh, 300px); overflow-y: auto; } .sidebar-736, .article-736 { overscroll-behavior: contain; } .sidebar-736 a { color: mediumvioletred; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <div class="container-736"> <div class="sidebar-736"> <a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 1</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 2</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 3</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 4</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 5</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 6</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 7</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 8</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 9</a><a href="https://moderncss.dev/12-modern-css-one-line-upgrades/">Nav item 10</a> </div> <div class="article-736"> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime nobis consectetur earum!</p> <p>Aliquid a praesentium quis in consequuntur mollitia laboriosam illum nemo commodi aut?</p> <p>Doloremque sapiente quos dignissimos sequi cupiditate commodi nemo non perspiciatis placeat totam.</p> <p>Reiciendis, at nulla! Hic nemo eius atque laborum consequuntur iusto exercitationem quasi.</p> <p>Est, assumenda amet culpa veritatis maxime debitis? Suscipit error amet quas sed?</p> <p>Magnam exercitationem neque error deleniti consequuntur, dolor repellat quo perferendis dicta sunt.</p> <p>Ipsum id repellat velit laudantium vel autem eos non aperiam qui nobis?</p> <p>Ratione maxime neque numquam minima, omnis dolorem temporibus laboriosam dolor atque suscipit?</p> <p>Dolores assumenda dolore similique eaque, odio voluptatibus. Aliquid iusto nostrum iste? Exercitationem.</p> <p>Quos adipisci ea ullam amet blanditiis voluptatibus, fuga laborum sed facere quasi!</p> <p>Odio rerum labore veritatis esse eaque quae debitis possimus ea omnis vitae.</p> <p>Quasi eum obcaecati laborum eaque nostrum numquam tempore reprehenderit qui beatae debitis?</p> <p>Optio atque fugit quia reprehenderit dolor rem et delectus praesentium ratione provident.</p> <p>Sed accusamus at architecto dolore minima error, assumenda amet nam perferendis odit?</p> <p>Et eius enim est hic doloribus reiciendis qui cupiditate? Autem, iure cupiditate?</p> </div> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="text-wrap">text-wrap</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#text-wrap" aria-labelledby="text-wrap"><span hidden="">#</span></a></div> <p>One of the newest properties (as of 2023) is <code>text-wrap</code>, which has two values that solve the type-setting problem of unbalanced lines. This includes preventing “orphans,” which describes a lonely word sitting by itself in the last text line.</p> <p>The first available value is <code>balance</code>, which has a goal of evening out the number of characters per line of text.</p> <p>There is a limitation of six lines of wrapped text, so the technique is best used on headlines or other shorter text passages. Limiting the scope of application also helps limit the impact on page rendering speed.</p> <details open=""> <summary>CSS for "Applying text-wrap: balance"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:is(h1, h2, h3, h4, .text-balance)</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token comment">/* For demonstration */</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 25ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .text-balance-888 { text-wrap: balance; } .demo-text-888 { max-inline-size: 25ch; padding: 0.25em; border: 1px dashed gray; font-family: "Baloo 2", system-ui; overflow: auto; resize: horizontal; } .demo-text-888:not(:first-child) { margin-block-start: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="demo-text-888 text-balance-888">This text has been balanced by text-wrap</p> <p class="demo-text-888">This text has not been balanced by text-wrap</p> </div> </div> <p>The other value of <code>pretty</code> specifically addresses preventing orphans and can be more broadly applied. The algorithm behind <code>pretty</code> will evaluate the last four lines in a text block to work out adjustments as needed to ensure the last line has two or more words.</p> <details open=""> <summary>CSS for "Applying text-wrap: balance"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> pretty<span class="token punctuation">;</span> <span class="token comment">/* For demonstration */</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 35ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .text-balance-114 { text-wrap: pretty; } .text-balance-114, .non-text-balance-114 { max-inline-size: 45ch; padding: 0.25em; border: 1px dashed gray; overflow: auto; resize: horizontal; } .demo-text-114 { font-family: "Baloo 2", system-ui; } .demo-text-114:not(:first-child) { margin-block-start: 1em; } </style> <div class="demo no-resize"> <div class="demo--content demo--padding"> <p class="demo-text-114"><strong>With text-wrap: pretty</strong></p> <p class="demo-text-114 text-balance-114">Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore cupiditate aliquid, facere explicabo voluptatibus iure! Saepe nostrum quasi corporis totam accusamus beatae obcaecati rerum fugiat minima perferendis voluptatibus.</p> <p class="demo-text-114"><strong>Without</strong></p> <p class="demo-text-114 non-text-balance-114">Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore cupiditate aliquid, facere explicabo voluptatibus iure! Saepe nostrum quasi corporis totam accusamus beatae obcaecati rerum fugiat minima perferendis voluptatibus.</p> </div> </div> <p>Use of <code>text-wrap</code> is a great progressive enhancement. However, any adjustments do 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> <div class="heading-wrapper h3"> <h3 id="scrollbar-gutter">scrollbar-gutter</h3> <a class="anchor" href="https://moderncss.dev/12-modern-css-one-line-upgrades/#scrollbar-gutter" aria-labelledby="scrollbar-gutter"><span hidden="">#</span></a></div> <p>In some scenarios, the appearance or disappearance of scrollbars can cause an unwanted layout shift. For example, when a dialog overlay is displayed and the background page adds <code>overflow: hidden</code> to prevent scrolling, causing a shift from removing the no longer needed scrollbars.</p> <p>The modern CSS property <code>scrollbar-gutter</code> enables a reservation of space for scrollbars in the layout, which prevents that undesirable shift. When there’s no need for a scrollbar, the browser will still paint a gutter as extra space created in addition to any padding on the scroll container.</p> <blockquote> <p><strong>Important</strong>: This property only has an effect when the user’s system settings are <em>not</em> for “overlay” scrollbars, as in the default for Mac OS, where the scrollbar only appears as an overlay on content that is actively being scrolled. Do not drop padding in favor of <code>scrollbar-gutter</code> since you will lose all intended space when overlay scrollbars are in use.</p> </blockquote> <p>Since this is visually apparent extra space, you may choose to assign this property using two keywords: <code>scrollbar-gutter: stable both-edges</code>. The use of <code>stable</code> by itself only adds a gutter where the scrollbar would otherwise be, while the addition of <code>both-edges</code> preferences adding the space to the opposite side of the scroll container, too. This ensures visual balance when the layout doesn’t yet need scrollbars to be visible. See <a href="https://defensivecss.dev/tip/scrollbar-gutter/">a visual of using scrollbar-gutter</a> from Ahmad Shadeed.</p> <hr /> <p>Be sure to review more of the articles here to upgrade your CSS knowledge even more! A great place to start is <a href="https://moderncss.dev/topics/">the topics list.</a></p> </content>
</entry>
<entry>
<title>How Custom Property Values are Computed</title>
<link href="https://moderncss.dev/how-custom-property-values-are-computed/"/>
<updated>2023-09-14T00:00:00Z</updated>
<id>https://moderncss.dev/how-custom-property-values-are-computed/</id>
<content type="html"><p>Custom properties - aka “CSS variables” - seem fairly straightforward. However, there are some behaviors to be aware of regarding how the browser computes the final values. A misunderstanding of this process may lead to an unexpected or missing value and difficulty troubleshooting and resolving the issue.</p> <p>To help you use custom properties confidently and troubleshoot efficiently, we’ll review:</p> <ul> <li>how the browser determines values for any property</li> <li>the impact of “computed value time”</li> <li>pitfalls around using custom properties with cutting-edge CSS</li> <li>why inheritance should inform your custom property architecture</li> <li>strategies to prevent invalid computed values</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="computed-inherited-and-initial-values">Computed, Inherited, and Initial Values</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#computed-inherited-and-initial-values" aria-labelledby="computed-inherited-and-initial-values"><span hidden="">#</span></a></div> <p>When the browser parses CSS, its goal is to calculate one value per property per element in the DOM.</p> <p>Something you learn early on about CSS is that you can change a property’s value multiple times from multiple rules that may select the same element.</p> <p>Given the HTML <code>&lt;h2 class=&quot;card__title&quot;&gt;</code>, all of the following are eligible matches for the <code>color</code> property.</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #222<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #74e<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__title</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #93b<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Each of these are <em>declared</em> values, and due to specificity and the cascading order, the element’s final selected value may be the <em>cascaded</em> value. In this case, <code>.card__title</code> wins for the <code>color</code> property.</p> <p>If a property does not receive a value from the cascade, then it will use either the <em>inherited</em> or <em>initial</em> value.</p> <ul> <li><em>Inherited</em> values come from the nearest ancestor that has assigned a value, if the property is <a href="https://web.dev/learn/css/inheritance/#which-properties-are-inheritable">allowed to inherit</a> (ex. <code>color</code>, font properties, <code>text-align</code>)</li> <li><em>Initial</em> values are used when no inherited value exists or is allowed and are the values provided by the specification for the property</li> </ul> <p>So, for <code>&lt;h2 class=&quot;card__title&quot;&gt;</code>, the full set of values populated for the element may be as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__title</span> <span class="token punctuation">{</span> <span class="token comment">/* Cascaded value */</span> <span class="token property">color</span><span class="token punctuation">:</span> #93b<span class="token punctuation">;</span> <span class="token comment">/* Initial properties and values */</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token comment">/* Inherited properties and values */</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Source Code Pro<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> 500<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Some property definitions require further computation to absolutize the values. The following are a few of the value transforms that may occur.</p> <ol> <li>Relative units such as <code>vw</code>, <code>em</code>, and <code>%</code> are converted to pixel values, and floats may be converted to integers</li> <li><code>currentColor</code> and named colors like <code>rebeccapurple</code> are converted to an <code>sRGB</code> value</li> <li>Compositing values that affect each other <ol> <li>ex. <code>padding: 1em</code> requires computing the value for <code>font-size</code> that <code>em</code> depends on</li> </ol> </li> <li>custom properties are replaced with their computed values</li> </ol> <p>These transformations result in the <em>computed,</em> <em>used</em>, and <strong><strong><strong>actual</strong></strong></strong> values - which refer to the progressive steps that may be involved to end up with an absolutized value. You can dive deeper into the specifics of <a href="https://www.w3.org/TR/css-cascade-4/#value-stages">how values are calculated</a> or check out this <a href="https://www.matuzo.at/blog/2023/100daysof-day82/">review of value processing</a>.</p> <div class="heading-wrapper h2"> <h2 id="custom-properties-and-computed-value-time">Custom Properties and Computed Value Time</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#custom-properties-and-computed-value-time" aria-labelledby="custom-properties-and-computed-value-time"><span hidden="">#</span></a></div> <p>One special computation scenario with a critical impact on modern CSS is when the browser assigns values to custom properties, referred to as “computed value time” (CVT).</p> <div class="heading-wrapper h3"> <h3 id="invalid-at-computed-value-time">Invalid at Computed Value Time</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#invalid-at-computed-value-time" aria-labelledby="invalid-at-computed-value-time"><span hidden="">#</span></a></div> <p>As described earlier, typically unfilled, or invalid property assignments will fall back to cascaded values when applicable.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Used due to the cascade */</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue <span class="token punctuation">}</span> <span class="token comment">/* Invalid as a "color", thrown out by the browser */</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Take a moment to see if you can determine what the <code>color</code> value of <code>.card p</code> will be in the following example.</p> <pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>.card p</code> will be the <em>inherited</em> <code>color</code> value of <code>red</code> as provided by the <code>body</code>. It is unable to use the cascaded value of <code>blue</code> due to the browser discarding that as a possible value candidate at “parse time” when it is only evaluating syntax. It is only when the user agent attempts to apply the final value - the stage of “computed value time” - that it realizes the value is invalid.</p> <p>Said another way: once the browser determines the cascaded value, which is partially based on syntactic correctness, it will trash any other candidates. For syntactically correct custom properties, the browser essentially assumes the absolutized value will succeed in being valid.</p> <p>This leads to an inability for custom properties to “fail early”. When there is a failure, the resulting value will be either an inherited value from an ancestor or the initial value for the property. (<em>If this sounds familiar, it’s because it’s also <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/unset">the behavior when using <code>unset</code></a>.</em>)</p> <p>Critically, this means <strong>an invalid custom property value is unable to fall back</strong> to a previously set cascaded value, as you may expect, because those have been discarded from the decision tree.</p> <p>All hope is not lost! If later a utility class on the paragraph were to update the <code>color</code> property, then due to rules of the cascade and specificity it would win out like normal and the invalid custom property value wouldn’t have an effect.</p> <pre class="language-css"><code class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #notacolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Not used */</span> <span class="token selector">.card p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Wins! */</span> <span class="token selector">.card .callout</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> purple <span class="token punctuation">}</span></code></pre> <p>Note that when referring to invalid values for custom properties that what makes it invalid is how the value is applied. For example, a space character is a valid custom property definition, but will be invalid when applied to a property.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Valid definition */</span> <span class="token property">--toggle</span><span class="token punctuation">:</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Invalid at computed time */</span> <span class="token property">margin</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--toggle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On the other hand, a custom property with a value of <code>100%</code> may be applied to <code>width</code> but not <code>color</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--length</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Valid */</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Invalid at computed time */</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="cvt-impact-on-modern-css-support">CVT Impact on Modern CSS Support</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#cvt-impact-on-modern-css-support" aria-labelledby="cvt-impact-on-modern-css-support"><span hidden="">#</span></a></div> <p>Another scenario where a custom property being invalid at computed value time may break your expectation is using the custom property as a partial value, or undefined with a fallback, especially paired with cutting-edge CSS features.</p> <p>Given the following, you may expect that when the <code>cqi</code> unit is not supported, the browser will simply use the prior <code>font-size</code> definition.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 1rem + 1.5vw<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Instead, the browser assumes it will understand the second <code>clamp()</code> definition and discards the prior <code>font-size</code> definitions for this <code>h2</code> rule. But when the browser goes to populate the custom property value and finds it doesn’t support <code>cqi</code>, it’s too late to use what was intended as the fallback definition. This means it instead uses the initial value, if there is no inheritable value from an ancestor.</p> <p>While you might think that the <em>initial</em> value would at least be a <code>font-size</code> befitting the <code>h2</code> level, the initial value for any element’s <code>font-size</code> is “<a href="https://drafts.csswg.org/css2/#valdef-font-size-medium">medium</a>” which is generally equivalent to <code>1rem</code>. This means you not only lose your intended fallback style, but also the visual hierarchy of the <code>h2</code> in browsers which do not support <code>cqi</code>.</p> <p><img src="https://moderncss.dev/img/posts/33/cvt-cqi-support.png" alt="Two type samples, where the top is in a browser that supports cqi and the font renders at a large size, whereas the bottom sample is in an unsupported browser for cqi and the font renders at the initial size of 1rem." /></p> <p>One way to discover the <code>initial</code> value for any property is to <a href="https://developer.mozilla.org/en-US/">search for it on MDN</a>, and look for the “Formal Definition” section which will list the initial value, as well as whether the value is eligible for inheritance.</p> <p>A few <code>initial</code> values to be aware of besides <code>font-size</code>:</p> <ul> <li><code>background-color</code>: <code>transparent</code></li> <li><code>border-color</code>: <code>currentColor</code></li> <li><code>border-width</code>: <code>medium</code> which equates to <code>3px</code></li> <li><code>color</code>: <code>canvastext</code> which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/system-color">a system color</a> and likely to be black, but may change due to forced-colors modes</li> <li><code>font-family</code>: depends on user agent, likely to be a serif</li> </ul> <div class="heading-wrapper h3"> <h3 id="safely-supporting-modern-css-values-in-custom-properties">Safely Supporting Modern CSS Values in Custom Properties</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#safely-supporting-modern-css-values-in-custom-properties" aria-labelledby="safely-supporting-modern-css-values-in-custom-properties"><span hidden="">#</span></a></div> <p>A safer solution is to wrap the definition using <code>cqi</code> in an <code>@supports</code> so that un-supporting browsers actually use the fallback.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 1rem + 1.5vw<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <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> <span class="token function">var</span><span class="token punctuation">(</span>--h2-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> 2.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Does this mean you need to change every place you use custom properties? That all depends on your support matrix (which browsers and versions you have elected to support). For super-ultra-modern properties, especially when the initial value is undesirable, this approach may be the safest. Another example of when you may use an <code>@supports</code> condition is with new color spaces, like <code>oklch()</code>.</p> <blockquote> <p><a href="https://moderncss.dev/testing-feature-support-for-modern-css/">Learn more about CSS feature detection</a> to help you choose the right route for your project.</p> </blockquote> <p>Confusingly, given a situation like the <code>cqi</code> example, browser dev tools for the un-supporting browser may still show the failing rule as being the applied style. This is likely because the browser may still support the other parts, like <code>clamp()</code>. An incorrect appearance in dev tools can make it difficult to troubleshoot issues caused by custom properties being invalid at computed time, which is why it’s important to fundamentally understand what is happening.</p> <div class="heading-wrapper h2"> <h2 id="inheritance-and-custom-properties">Inheritance and Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#inheritance-and-custom-properties" aria-labelledby="inheritance-and-custom-properties"><span hidden="">#</span></a></div> <p>Another way computed value time affects custom property value assignment is inheritance of computed values.</p> <p>Calculation of a custom property value is performed once per element, which then makes the computed value available for inheritance. Let’s learn how that impacts your custom property architecture choices.</p> <div class="heading-wrapper h3"> <h3 id="inheritable-values-become-immutable">Inheritable Values Become Immutable</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#inheritable-values-become-immutable" aria-labelledby="inheritable-values-become-immutable"><span hidden="">#</span></a></div> <p>A common convention is batching custom properties into the <code>:root</code> selector. If one of those properties involves a calculation which includes another <code>:root</code>-level custom property, then updating the modifying property from a descendent will not update the calculation.</p> <p>As in the following example, the <code>--font-size-large</code> is calculated immediately, so updating the <code>--font-size</code> property within a descendent rule will not be able to affect the value.</p> <details open=""> <summary>CSS for "Computed values are immutable"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">--font-size-large</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>2 * <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token comment">/* The new --font-size will not update the --font-size-large calculation */</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-large<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .font-size-demo-380 { --font-size: 1rem; --font-size-large: calc(2 * var(--font-size)); } .h1-380 { --font-size: 1.25rem; /* The new --font-size will not update the --font-size-large calculation */ font-size: var(--font-size-large); } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="font-size-demo-380"> <p class="h1-380">Cake muffin toffee gingerbread ice cream</p> </div> </div> </div> <p>This is because the calculation happens as soon as the browser processes the definition against the <code>:root</code>. So the <code>:root</code> definition produces a static, computed value which is inheritable, but immutable.</p> <p>This is not to say this behavior is unique to <code>:root</code>. The key concept is that once custom property values are computed, the <em>computed value</em> is only inheritable.</p> <p>To think about it another way: within the cascade, values can be inherited by descendents, but can’t pass values back to their ancestors. Essentially this is why the computed custom property value on an ancestor element cannot be modified by a descendent element.</p> <div class="heading-wrapper h3"> <h3 id="enabling-extendable-custom-property-values">Enabling Extendable Custom Property Values</h3> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#enabling-extendable-custom-property-values" aria-labelledby="enabling-extendable-custom-property-values"><span hidden="">#</span></a></div> <p>If we lower the custom property calculation to be applied based on classes, then the browser will be able to recalculate as part of the value processing to determine the computed value. This is because it will calculate a value for elements with the class <code>font-resize</code>, and a separate value for elements with both <code>font-resize</code> and <code>font-large</code> classes.</p> <details open=""> <summary>CSS for "Computed values are per element"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.font-resize</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-adjust<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.font-large</span> <span class="token punctuation">{</span> <span class="token comment">/* Successfully modifies the value when paired with .font-resize */</span> <span class="token property">--font-size-adjust</span><span class="token punctuation">:</span> 2.5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .font-size-demo-717 { --font-size: 1rem; } .font-resize-717 { font-size: calc(var(--font-size-adjust, 1) * var(--font-size)); } .font-large-717 { /* Successfully modifies the value when paired with .font-resize-717 */ --font-size-adjust: 2.5; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="font-size-demo-717"> <p class="font-resize-717 font-large-717">Cake muffin toffee gingerbread ice cream</p> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="preventing-invalid-at-computed-value-time">Preventing Invalid at Computed Value Time</h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#preventing-invalid-at-computed-value-time" aria-labelledby="preventing-invalid-at-computed-value-time"><span hidden="">#</span></a></div> <p>A few simple strategies to avoid custom property failure include:</p> <ul> <li>Use a fallback value when defining a custom property, which is the second value that can be provided to the <code>var()</code> function, ex. <code>var(--my-property, 1px)</code></li> <li>Ensure fallback values within <code>var()</code> are of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types">the correct type</a> for the property, or <a href="https://moderncss.dev/how-custom-property-values-are-computed/#provide-a-custom-initial-value-with-property">define your own with <code>@property</code></a></li> <li>If you’re using a polyfill for a new feature, check that it resolves usage in custom properties as you expect</li> <li>Use <code>@supports</code> to ensure your intended modern CSS upgrade doesn’t break intended fallback rules in un-supporting browsers</li> </ul> <p>And as always - test your solutions in as many browsers and on as many devices as you can!</p> <div class="heading-wrapper h2"> <h2 id="provide-a-custom-intial-value-with-property">Provide a custom intial value with <code>@property</code></h2> <a class="anchor" href="https://moderncss.dev/how-custom-property-values-are-computed/#provide-a-custom-intial-value-with-property" aria-labelledby="provide-a-custom-intial-value-with-property"><span hidden="">#</span></a></div> <p>A cross-browser feature as of the release of Firefox 128 in July 2024 is a new at-rule - <code>@property</code> - which allows defining types for your custom properties.</p> <p>Helpfully, the <code>initial-value</code> parameter enables defining your own property-specific initial value which will be used in the event the computed value would otherwise be invalid!</p> <p>Given the following definition for our <code>--color-primary</code> custom property, if the computed value was invalid, the provided <code>initial-value</code> of <code>purple</code> would be used instead.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color-primary</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Learn more about how to use <code>@property</code> in the Modern CSS article &quot;<a href="https://moderncss.dev/providing-type-definitions-for-css-with-at-property/">Providing Type Definitions for CSS with @property</a>.&quot;</p> </content>
</entry>
<entry>
<title>Modern CSS For Dynamic Component-Based Architecture</title>
<link href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/"/>
<updated>2023-06-09T00:00:00Z</updated>
<id>https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/</id>
<content type="html"><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> <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> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="css-reset-additions">CSS Reset Additions</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-reset-additions" aria-labelledby="css-reset-additions"><span hidden="">#</span></a></div> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Baseline for default links */</span> <span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token comment">/* Relatively sized thickness and offset */</span> <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> <span class="token property">text-underline-offset</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <p>As for our reset, this means our visible focus styles will be attached to only the <code>:focus-visible</code> state.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:focus-visible</span> <span class="token punctuation">{</span> <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> <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> <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> <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> <span class="token punctuation">}</span></code></pre> <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> <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> <blockquote> <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> </blockquote> <p>The last two additions to my reset involve improving the scroll position for targeted or focused elements.</p> <p>Using <code>scroll-padding</code> properties, you can adjust the scroll position in relation to elements. The &quot;padding&quot; space does not affect the layout, just the offset of the scroll position.</p> <p>In this rule, the <code>:target</code> selector matches when an element is a target of an anchor link, also known as a &quot;document fragment.&quot; The <code>scroll-padding-block-start</code> will allow for room between the target and the top of the viewport.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Scroll margin allowance above anchor links */</span> <span class="token selector">:target</span> <span class="token punctuation">{</span> <span class="token property">scroll-padding-block-start</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The use of <code>scroll-padding-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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Scroll margin allowance below focused elements to ensure they are clearly in view */</span> <span class="token selector">:focus</span> <span class="token punctuation">{</span> <span class="token property">scroll-padding-block-end</span><span class="token punctuation">:</span> 8vh<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <div class="heading-wrapper h2"> <h2 id="project-architecture">Project Architecture</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#project-architecture" aria-labelledby="project-architecture"><span hidden="">#</span></a></div> <p>Next up are two features with the potential to strongly impact your project architecture: nesting and cascade layers.</p> <div class="heading-wrapper h3"> <h3 id="css-nesting">CSS Nesting</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-nesting" aria-labelledby="css-nesting"><span hidden="">#</span></a></div> <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> <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> <p>Any selector type is valid as a nested selector. 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.</p> <p>Selectors such as <code>:is()</code> or <code>:where()</code> are useful in nested selectors. And standard class or attribute selection is also allowed, as well as the other combinators.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">a</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp; a</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">:is(a, button)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">.button</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">+ .another-element</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp;[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/* Results in: */</span> <span class="token selector">.my-element [data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">.my-element[data-type]</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>&amp;</code> also allows nested selectors for pseudo-elements and pseudo-classes.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.my-element</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::before</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:hover</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <div class="heading-wrapper h3"> <h3 id="css-cascade-layers">CSS Cascade Layers</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-cascade-layers" aria-labelledby="css-cascade-layers"><span hidden="">#</span></a></div> <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 &quot;C&quot; 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> <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> <p>The critical rules to understand about cascade layers are:</p> <ul> <li>the initial order of layers defines the applied priority order <ul> <li>priority increases in order</li> <li>ex. first layer has less priority than the last layer</li> </ul> </li> <li>less-nested layered styles have priority over deeper nested layer styles</li> <li>un-layered styles have the highest priority over layered styles</li> </ul> <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> <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> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> typography</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> colors</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> pink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@layer</span> global</span> <span class="token punctuation">{</span> <span class="token selector">p</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <blockquote> <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> </blockquote> <p>As with many newer features, there is much room for experimentation, and &quot;best practices&quot; or &quot;standards of use&quot; 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> <p>Here’s a layer order I have been trying out in my own projects:</p> <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> <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> <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> <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> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="theming-and-branding">Theming and Branding</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#theming-and-branding" aria-labelledby="theming-and-branding"><span hidden="">#</span></a></div> <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> <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> <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> <p>For the remainder of the examples, we’ll be building up components and branding for our imaginary product Jaberwocky.</p> <p><img src="https://moderncss.dev/img/posts/32/brand.png" alt="" /></p> <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> <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> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Color styles */</span> <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> <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> <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> <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> <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> <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> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>You may also wish to place font sizes or other &quot;tokens&quot; 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> <p>Now that we have a brand and color palette, it's time to add the other two features.</p> <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 &quot;light&quot; 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> <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> <blockquote> <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> </blockquote> <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> <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> <span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">color-scheme</span><span class="token punctuation">:</span> light<span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="layout">Layout</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#layout" aria-labelledby="layout"><span hidden="">#</span></a></div> <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> <div class="heading-wrapper h3"> <h3 id="css-grid-layout">CSS Grid Layout</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-grid-layout" aria-labelledby="css-grid-layout"><span hidden="">#</span></a></div> <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> <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> <details open=""> <summary>CSS for "CSS Grid Layout"</summary> <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> <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <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> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .layout-grid-51 { display: grid; grid-template-columns: repeat( auto-fit, minmax(min(100%, 30ch), 1fr) ); gap: 2cqi; } .layout-grid-51 > span { padding: 2%; text-align: center; font-size: 2cqi; background-color: lightblue; border: 2px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="layout-grid-51"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></div> </div> </div> <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> <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> <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> <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 &quot;breakpoint&quot; 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> <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> <span class="token selector">.layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--layout-grid-min</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token property">--layout-grid-gap</span><span class="token punctuation">:</span> 3vw<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <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> <span class="token punctuation">)</span><span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <div class="heading-wrapper h3"> <h3 id="css-flexbox-layout">CSS Flexbox Layout</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-flexbox-layout" aria-labelledby="css-flexbox-layout"><span hidden="">#</span></a></div> <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> <p>The flexbox grid utility shares two common features with the CSS grid utility: defining a minimum &quot;column&quot; size and the <code>gap</code> size.</p> <p>With CSS grid, the parent controls the child size. But with flexbox, the children control their sizing. Therefore, it's more safe to use a non-relative value for the column size, since using a font-relative unit like <code>ch</code> may change the outcome depending on the child's own <code>font-size</code>.</p> <p>Beyond the custom properties API, 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> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> 20rem<span class="token punctuation">;</span> <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> 3vmax<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <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> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>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> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <details open=""> <summary>CSS for "CSS Flexbox Grid Layout"</summary> <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> <span class="token selector">.flex-layout-grid</span> <span class="token punctuation">{</span> <span class="token property">--flex-grid-min</span><span class="token punctuation">:</span> 20rem<span class="token punctuation">;</span> <span class="token property">--flex-grid-gap</span><span class="token punctuation">:</span> 3vmax<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <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> <span class="token selector">> *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-553 { --flex-grid-min: 20rem; --flex-grid-gap: 3vmax; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); } .flex-layout-grid-553 > * { flex: 1 1 var(--flex-grid-min); } .flex-layout-grid-553 > span { padding: 2%; text-align: center; font-size: 2cqi; background-color: lightblue; border: 2px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="flex-layout-grid-553"><span>Item 1</span><span>Item 2</span><span>Item 3</span><span>Item 4</span><span>Item 5</span></div> </div> </div> <div class="heading-wrapper h3"> <h3 id="prepare-for-container-queries">Prepare for Container Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#prepare-for-container-queries" aria-labelledby="prepare-for-container-queries"><span hidden="">#</span></a></div> <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> <p>To correctly query against the size of flexbox or grid children, we can enhance our utilities to include container definitions.</p> <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> <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> <span class="token selector">:is(.layout-grid, .flex-layout-grid) > *</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Later examples will demonstrate how to use features of container size queries and make use of these layout utility containers.</p> <blockquote> <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> </blockquote> <div class="heading-wrapper h2"> <h2 id="component-buttons">Component: Buttons</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-buttons" aria-labelledby="component-buttons"><span hidden="">#</span></a></div> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-buttons.png" alt="" /></p> <p>Our styles will support the following variations of a button:</p> <ul> <li>a button element</li> <li>a link element</li> <li>text plus an icon</li> <li>icon plus text</li> <li>icon-only</li> </ul> <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> <div class="heading-wrapper h3"> <h3 id="custom-property-and-component-apis">Custom Property and Component APIs</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#custom-property-and-component-apis" aria-labelledby="custom-property-and-component-apis"><span hidden="">#</span></a></div> <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> <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> <p>Then, we use the values that will signify the &quot;default&quot; 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> <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> <span class="token selector">.button</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="creating-variant-styles-with-has">Creating Variant Styles with <code>:has()</code></h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#creating-variant-styles-with-has" aria-labelledby="creating-variant-styles-with-has"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span> <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> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This variant exposes a <code>--button-padding</code> custom property, and sets an explicit <code>border-radius</code>.</p> <details open=""> <summary>CSS for "Button Component"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.button</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token selector">.button:where(:has(.icon))</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:has(.inclusively-hidden))</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:not(:has(.icon)))</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">min-inline-size</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.button:where(:not(:has(.inclusively-hidden)))</span> <span class="token punctuation">{</span> <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> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> :root { /* Color styles */ --demo-primary: hsl(265, 38%, 13%); --demo-secondary: hsl(283, 6%, 45%); --demo-tertiary: hsl(257, 15%, 91%); --demo-light: hsl(270, 100%, 99%); --demo-accent: hsl(278, 100%, 92%); --demo-accent--alt: hsl(279, 100%, 97%); --demo-accent--ui: hsl(284, 55%, 66%); } .demo-container-162 { display: grid; gap: 1rem; justify-content: start; padding: 1rem; } .inclusively-hidden-162 { clip-path: inset(50%); height: 1px; width: 1px; overflow: hidden; position: absolute; white-space: nowrap; } .btn-162 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-162:where(:has(.icon-162)) { display: flex; gap: 0.5em; align-items: center; } .btn-162:where(:has(.inclusively-hidden-162)) { border-radius: 50%; padding: 0.5em; } .btn-162:where(:not(:has(.icon-162))) { text-align: center; min-inline-size: 10ch; } .btn-162:where(:not(:has(.inclusively-hidden-162))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } </style> <div class="demo no-resize"> <div class="demo--content"> <div class="demo-container-162"> <button type="button" class="btn-162">Button</button> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/" class="btn-162">Link</a> <button type="button" class="btn-162"> Text + Icon Button <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> </button> <button type="button" class="btn-162"> <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> Icon + Text button </button> <button type="button" class="btn-162"> <svg class="icon-162" aria-hidden="true" width="24" height="24"> <use href="#star"></use> </svg> <span class="inclusively-hidden-162">Icon only button</span> </button> </div> </div> </div> <svg hidden=""> <defs> <symbol viewBox="0 0 256 256" id="star"> <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> </symbol> </defs> </svg> <div class="heading-wrapper h3"> <h3 id="using-custom-properties-api-for-states">Using Custom Properties API for States</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#using-custom-properties-api-for-states" aria-labelledby="using-custom-properties-api-for-states"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.button:hover</span> <span class="token punctuation">{</span> <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> <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> <span class="token punctuation">}</span> <span class="token selector">.button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">--outline-style</span><span class="token punctuation">:</span> dashed<span class="token punctuation">;</span> <span class="token property">--outline-offset</span><span class="token punctuation">:</span> -0.35em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="component-card">Component: Card</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-card" aria-labelledby="component-card"><span hidden="">#</span></a></div> <p><img src="https://moderncss.dev/img/posts/32/preview-cards.png" alt="" /></p> <p>For the card component, we have three variants and one state to manage:</p> <ul> <li>default, small card</li> <li>“new” style</li> <li>wide with larger text</li> <li>focus-visible state</li> </ul> <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> <p>Here are a few other details about our cards and expected usage:</p> <ul> <li>we’ll place them within our flexbox-based layout grid</li> <li>the layout grid will be within a wrapping container</li> </ul> <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> <details false=""> <summary>CSS for "Base Card Styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span> <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> <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> <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> <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> <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> <span class="token property">background-size</span><span class="token punctuation">:</span> 28px 28px<span class="token punctuation">;</span> <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> <span class="token property">padding</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <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> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">align-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> space-between<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon::before</span> <span class="token punctuation">{</span> <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> <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> <span class="token property">font-weight</span><span class="token punctuation">:</span> 600<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon::before, .card__number-icon img</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 2.25rem<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon img</span> <span class="token punctuation">{</span> <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> <span class="token property">padding</span><span class="token punctuation">:</span> 0.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <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> <span class="token punctuation">}</span> <span class="token selector">.card a::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">inset</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> 400<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-953 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-953 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-953 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-953 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-953 { display: flex; justify-content: space-between; } .card__number-icon-953::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-953::before, .card__number-icon-953 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-953 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-953 a { text-decoration: none; color: var(--demo-primary); } .card-953 a::before { content: ""; position: absolute; inset: 0; } .card-953 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-953 a { font-size: inherit; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-953"> <li> <div class="card-953"> <div data-num="1" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-953">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-953"> <div data-num="2" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-953"> <div data-num="3" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-953"> <div data-num="4" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-953"> <div data-num="5" class="card__number-icon-953"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="styling-based-on-element-presence">Styling Based on Element Presence</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#styling-based-on-element-presence" aria-labelledby="styling-based-on-element-presence"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token selector">&amp;:has(.tag)</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "&#39;New&#39; Card"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card:has(.tag)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span> <span class="token selector">.card :is(h2, h3):has(.tag)</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">justify-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-370 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-370 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-370 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-370 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-370 { display: flex; justify-content: space-between; } .card__number-icon-370::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-370::before, .card__number-icon-370 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-370 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-370 a { text-decoration: none; color: var(--demo-primary); } .card-370 a::before { content: ""; position: absolute; inset: 0; } .card-370 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-370 a { font-size: inherit; } .card-370:has(.tag-370) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-370 :is(h2, h3):has(.tag-370) { display: grid; gap: 0.25em; justify-items: start; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-370"> <li> <div class="card-370"> <div data-num="1" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-370">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-370"> <div data-num="2" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-370"> <div data-num="3" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-370"> <div data-num="4" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-370"> <div data-num="5" class="card__number-icon-370"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="special-focus-styling">Special Focus Styling</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#special-focus-styling" aria-labelledby="special-focus-styling"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card a:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">--outline-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card States"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card:focus-within</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid #b77ad0<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -6px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a:is(:focus, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card a:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-826 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-826 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-826 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-826 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-826 { display: flex; justify-content: space-between; } .card__number-icon-826::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-826::before, .card__number-icon-826 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-826 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-826 a { text-decoration: none; color: var(--demo-primary); } .card-826 a::before { content: ""; position: absolute; inset: 0; } .card-826 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-826 a { font-size: inherit; } .card-826:has(.tag-826) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-826 :is(h2, h3):has(.tag-826) { display: grid; gap: 0.25em; justify-items: start; } .card-826:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-826 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-826 a:is(:hover, :focus-visible) { text-decoration: underline; } </style> <div class="demo"> <div class="demo--content"> <ul role="list" class="flex-layout-grid-826"> <li> <div class="card-826"> <div data-num="1" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-826">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-826"> <div data-num="2" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-826"> <div data-num="3" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-826"> <div data-num="4" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-826"> <div data-num="5" class="card__number-icon-826"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> <div class="heading-wrapper h3"> <h3 id="context-based-container-queries">Context-Based Container Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#context-based-container-queries" aria-labelledby="context-based-container-queries"><span hidden="">#</span></a></div> <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> <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> <p><img src="https://moderncss.dev/img/posts/32/spec-cards.png" alt="" /></p> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token comment">/* Wide variation container size query */</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <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 > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token comment">/* Keep other styles */</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token property">max-inline-size</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card Container Queries"</summary> <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 > 80ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> grid-item <span class="token punctuation">(</span>inline-size > 35ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card__number-icon</span> <span class="token punctuation">{</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-861 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-861 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-861 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-861 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-861 { display: flex; justify-content: space-between; } .card__number-icon-861::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-861::before, .card__number-icon-861 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-861 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-861 a { text-decoration: none; color: var(--demo-primary); } .card-861 a::before { content: ""; position: absolute; inset: 0; } .card-861 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-861 a { font-size: inherit; } .card-861:has(.tag-861) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-861 :is(h2, h3):has(.tag-861) { display: grid; gap: 0.25em; justify-items: start; } .card-861:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-861 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-861 a:is(:hover, :focus-visible) { text-decoration: underline; } .layout-861 { container: layout-container / inline-size; } @container layout-container (inline-size > 80ch) { .card-861 { aspect-ratio: 4/3; max-width: 100%; } } @container grid-item (inline-size > 35ch) { .card-861 { grid-auto-flow: column; align-items: center; justify-content: start; gap: 5cqi; aspect-ratio: unset; } .card__number-icon-861 { flex-direction: column; gap: 1rem; } } </style> <div class="demo"> <div class="demo--content"> <div class="layout-861"> <ul role="list" class="flex-layout-grid-861"> <li> <div class="card-861"> <div data-num="1" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-861">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-861"> <div data-num="2" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-861"> <div data-num="3" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-861"> <div data-num="4" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-861"> <div data-num="5" class="card__number-icon-861"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> </div> <div class="heading-wrapper h3"> <h3 id="container-query-fluid-type">Container Query Fluid Type</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#container-query-fluid-type" aria-labelledby="container-query-fluid-type"><span hidden="">#</span></a></div> <p>According to our mockup, the last adjustment we need is to increase the font size as the card becomes wider.</p> <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> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <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> <span class="token punctuation">}</span></code></pre> <blockquote> <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> </blockquote> <p>One last modern CSS feature we'll use to conclude our card styles will upgrade the headings. Use of <code>text-wrap: balance</code> will evaluate a text block of up to six lines and &quot;balance&quot; 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> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Card Fluid Type"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.card :is(h2, h3)</span> <span class="token punctuation">{</span> <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> <span class="token property">text-wrap</span><span class="token punctuation">:</span> balance<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .flex-layout-grid-717 { --flex-grid-min: var(--layout-column-min); --flex-grid-gap: 1.25rem; display: flex; flex-wrap: wrap; gap: var(--flex-grid-gap); padding: 1rem; } .flex-layout-grid-717 li { flex: 1 1 25ch; container: var(--grid-item-container, grid-item) / inline-size; } .tag-717 { padding: 0.25em 0.35em; font-size: max(0.75em, 0.8rem); font-weight: 600; line-height: 1; background-color: var(--tag-bg, var(--demo-secondary)); color: var(--tag-color, var(--demo-accent--alt)); border-radius: 0.25rem; } .card-717 { --card-bg: var(--demo-light); --dot-color: color-mix(in hsl, var(--demo-primary), transparent 95%); background-color: var(--card-bg); background-image: radial-gradient(var(--dot-color) 10%, transparent 12%), radial-gradient(var(--dot-color) 11%, transparent 13%); background-size: 28px 28px; background-position: 0 0, 72px 72px; padding: 1rem; border: 1px solid var(--demo-primary); position: relative; height: 100%; display: grid; gap: 1rem; align-content: space-between; } .card__number-icon-717 { display: flex; justify-content: space-between; } .card__number-icon-717::before { content: "0" attr(data-num); background-color: var(--demo-accent); font-weight: 600; font-size: 1.15rem; } .card__number-icon-717::before, .card__number-icon-717 img { width: 2.25rem; aspect-ratio: 1; display: grid; place-content: center; } .card__number-icon-717 img { border: 2px solid var(--demo-tertiary); padding: 0.15rem; } .card-717 a { text-decoration: none; color: var(--demo-primary); } .card-717 a::before { content: ""; position: absolute; inset: 0; } .card-717 :is(h2, h3) { font-weight: 400; font-size: 1.25rem; } .card-717 a { font-size: inherit; } .card-717:has(.tag-717) { box-shadow: inset 0 0 0 4px var(--demo-accent); } .card-717 :is(h2, h3):has(.tag-717) { display: grid; gap: 0.25em; justify-items: start; } .card-717:focus-within { outline: 3px solid #b77ad0; outline-offset: -6px; } .card-717 a:is(:focus, :focus-visible) { outline: 1px solid transparent; } .card-717 a:is(:hover, :focus-visible) { text-decoration: underline; } .layout-717 { container: layout-container / inline-size; } @container layout-container (inline-size > 80ch) { .card-717 { aspect-ratio: 4/3; max-width: 100%; } } @container grid-item (inline-size > 35ch) { .card-717 { grid-auto-flow: column; align-items: center; justify-content: start; gap: 5cqi; aspect-ratio: unset; } .card__number-icon-717 { flex-direction: column; gap: 1rem; } } .card-717 :is(h2, h3) { font-size: clamp(1.25rem, 5cqi, 1.75rem); text-wrap: balance; } </style> <div class="demo"> <div class="demo--content"> <div class="layout-717"> <div class="layout-717"> <ul role="list" class="flex-layout-grid-717"> <li> <div class="card-717"> <div data-num="1" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/analyze.svg" alt="" /> </div> <h3> <span class="tag-717">New</span> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Wafers caramels candy</a> </h3> </div> </li> <li> <div class="card-717"> <div data-num="2" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/arrange.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Apple pie sweet lollipop</a></h3> </div> </li> <li> <div class="card-717"> <div data-num="3" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/copy.svg" alt="" /> </div> <h3> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cheesecake topping croissant cupcake</a> </h3> </div> </li> <li> <div class="card-717"> <div data-num="4" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/group.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Lollipop chocolate cake</a></h3> </div> </li> <li> <div class="card-717"> <div data-num="5" class="card__number-icon-717"> <img src="https://moderncss.dev/img/posts/32/icons/join.svg" alt="" /> </div> <h3><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/">Cotton candy ice cream</a></h3> </div> </li> </ul> </div> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="component-pagination">Component: Pagination</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-pagination" aria-labelledby="component-pagination"><span hidden="">#</span></a></div> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-pagination.png" alt="" /></p> <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> <p>In slightly wider spaces, the labels for the “Previous” and “Next” controls will be visible.</p> <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> <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">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/></span></span> <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">></span></span>Previous<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">></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">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token comment">&lt;!-- pagination links --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token punctuation">/></span></span> <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">></span></span>Next<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <p>We'll first define containment for the <code>.pagination-container</code> to enable this dynamic layout behavior.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Container Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.pagination-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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> <span class="token selector">.pagination-nav__label</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-337 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-337 a { text-decoration: none; } .pagination-container-337, .pagination-list-337 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-337 { gap: 0.25rem; } .pagination-list-337 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-337 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-337 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-337 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-337:hover .pagination-nav__label-337 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-337 svg { transform: scaleX(-1); } .pagination-label-337 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-337 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-337 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-337 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-337 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-337 { display: grid; } .pagination-label-337 { display: none; } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-337" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-337 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-337">Previous</span> </a> <span class="pagination-label-337">Page 3 of 10</span> <ul role="list" class="pagination-list-337"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-337 pagination-nav__next-337"> <span class="pagination-nav__label-337">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <div class="heading-wrapper h3"> <h3 id="using-has-for-quantity-queries">Using <code>:has()</code> for Quantity Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#using-has-for-quantity-queries" aria-labelledby="using-has-for-quantity-queries"><span hidden="">#</span></a></div> <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> <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> <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> <p>We must place this rule within the &quot;large&quot; 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> <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> <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Quantity Queries"</summary> <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> <span class="token selector">.pagination-container:has(li:nth-child(11))</span> <span class="token punctuation">{</span> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-416 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-416 a { text-decoration: none; } .pagination-container-416, .pagination-list-416 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-416 { gap: 0.25rem; } .pagination-list-416 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-416 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-416 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-416 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-416:hover .pagination-nav__label-416 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-416 svg { transform: scaleX(-1); } .pagination-label-416 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-416 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-416 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-416 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-416 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-416 { display: grid; } .pagination-label-416 { display: none; } } @container (min-width: 40ch) { .pagination-container-416:has(li:nth-child(11)) { .pagination-list-416 { display: none; } .pagination-label-416 { display: block; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-416" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-416 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-416">Previous</span> </a> <span class="pagination-label-416">Page 3 of 12</span> <ul role="list" class="pagination-list-416"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">11</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">12</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-416 pagination-nav__next-416"> <span class="pagination-nav__label-416">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <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> <div class="heading-wrapper h3"> <h3 id="upgrading-to-style-queries">Upgrading to Style Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#upgrading-to-style-queries" aria-labelledby="upgrading-to-style-queries"><span hidden="">#</span></a></div> <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> <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> <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> <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> <span class="token comment">/* Styles for the container's children */</span> <span class="token punctuation">}</span></code></pre> <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> <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> <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Pagination Style Queries"</summary> <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> <span class="token property">--show-label</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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> <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> <span class="token selector">.pagination-list</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.pagination-label</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .pagination-container-23 { gap: 1rem; padding: 3rem 0; font-size: 1.65rem; } .pagination-container-23 a { text-decoration: none; } .pagination-container-23, .pagination-list-23 { display: grid; grid-auto-flow: column; justify-content: center; align-items: center; } .pagination-list-23 { gap: 0.25rem; } .pagination-list-23 a { display: grid; place-content: center; padding: 0.15em; border-radius: 0.25em; border: 2px solid transparent; line-height: 1; width: 3ch; height: 3ch; font-weight: bold; text-align: center; color: var(--demo-primary); } .pagination-list-23 a:is([aria-current], :hover) { border-color: var(--demo-accent--ui); background-color: var(--demo-accent--alt); } .pagination-nav-23 { display: inline-flex; align-items: center; color: var(--demo-secondary); border-radius: 0.25em; } .pagination-nav-23 svg { width: 1.5em; height: 1.5em; flex-shrink: 0; } .pagination-nav-23:hover .pagination-nav__label-23 { text-decoration: underline; text-underline-offset: 0.15em; } .pagination-nav__next-23 svg { transform: scaleX(-1); } .pagination-label-23 { font-weight: bold; text-align: center; } /* Most narrow display */ .pagination-list-23 { display: none; } /* Inclusively hidden so it is still read as a label by assistive tech */ .pagination-nav__label-23 { height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); } .pagination-container-23 { container-type: inline-size; } @container (min-width: 25ch) { .pagination-nav__label-23 { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container (min-width: 40ch) { .pagination-list-23 { display: grid; } .pagination-label-23 { display: none; } } .pagination-container-23:has(li:nth-child(11)) { --show-label: true; } @container (min-width: 40ch) { @container style(--show-label: true) { .pagination-list-23 { display: none; } .pagination-label-23 { display: block; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="pagination-container-23" aria-label="Pagination"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-23 pagination-nav__prev"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> <span class="pagination-nav__label-23">Previous</span> </a> <span class="pagination-label-23">Page 3 of 12</span> <ul role="list" class="pagination-list-23"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">1</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">2</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">3</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">4</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">5</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">6</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">7</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">8</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">9</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">10</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">11</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">12</a></li> </ul> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="pagination-nav-23 pagination-nav__next-23"> <span class="pagination-nav__label-23">Next</span> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.6 3a1.3 1.3 0 0 0-1.8 0l-8.3 8.3a1 1 0 0 0 0 1.4l8.3 8.3a1.2 1.2 0 1 0 1.8-1.7L9.4 12l7.2-7.3c.5-.4.5-1.2 0-1.7z"></path></svg> </a> </nav> </div> </div> <div class="heading-wrapper h2"> <h2 id="component-navigation">Component: Navigation</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#component-navigation" aria-labelledby="component-navigation"><span hidden="">#</span></a></div> <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 &quot;Login&quot; and &quot;Sign Up&quot; placed on the opposite side.</p> <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> <p><img src="https://moderncss.dev/img/posts/32/preview-navigation.png" alt="" /></p> <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> <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> <p><img src="https://moderncss.dev/img/posts/32/spec-navigation.png" alt="" /></p> <p>Here's our markup outline to help understand the relationships between our elements.</p> <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">></span></span> <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">></span></span>Logo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">></span></span> <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">></span></span> Menu <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token comment">&lt;!-- link list --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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">></span></span> <span class="token comment">&lt;!-- Login / Sign Up --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <blockquote> <p>You'll likely find that building with container queries in mind may prompt rethinking your HTML structure and simplifying the hierarchy.</p> </blockquote> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> auto 1fr auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <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> <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 >= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <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 >= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Navigation Container Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> navigation / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> menu / inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> navigation <span class="token punctuation">(</span>inline-size >= 45ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__brand span</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> unset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> menu <span class="token punctuation">(</span>inline-size >= 60ch<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .btn-102 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-102 { color: var(--button-color, var(--demo-primary)); background-color: var(--button-bg, var(--demo-accent)); font-size: 1rem; } .btn-102:where([aria-expanded], :has(.icon-102)) { display: flex; gap: 0.5em; align-items: center; } .btn-102:where(:not(:has(.icon-102))) { text-align: center; min-inline-size: 10ch; } .btn-102:where(:not(:has(.inclusively-hidden-102))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } .btn--small-102 { --button-padding: 0.35em 0.75em; } .btn--inverse-102 { --button-bg: var(--demo-light); } .btn-102:is([aria-expanded])::after { content: ""; display: block; width: 1em; height: 0.5em; background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } .navigation-102 { margin-block: 1rem; padding: 1rem; background-color: var(--demo-light); box-shadow: 0 0.25rem 0.35rem -0.15rem rgba(166, 166, 166, 0.45); display: grid; grid-template-columns: auto 1fr auto; gap: 1rem; align-items: center; } .navigation-102 a:not(.btn-102) { text-decoration: none; padding: 0.15em 0.25em; width: fit-content; color: var(--demo-primary); border-bottom: 2px solid transparent; } .navigation-102 a:not(.btn-102):hover { background-color: var(--demo-accent); } .navigation-102 a:not(.btn-102)[aria-current] { border-bottom-color: var(--demo-accent--ui); } .navigation__brand-102 { display: inline-flex; align-items: baseline; gap: 0.25em; font-weight: bold; font-size: 1.25rem; transform: translateY(-0.1em); } .navigation__brand-102 svg { width: 1.25em; height: 1.25em; transform: translateY(0.15em); } .navigation__brand-102 span { /* Inclusively hidden so it is still read as a label by assistive tech */ height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); font-size: inherit; line-height: 0; } .navigation__menu-102 :is(button, a) { letter-spacing: 0.03em; font-weight: 600; color: var(--primary); } .navigation__menu-102 ul { gap: 0.5rem; } .btn-102:where([aria-expanded=false]) + ul { display: none; } .navigation__actions-102 { display: flex; gap: 0.5rem; align-items: center; } .navigation-102 { container: navigation / inline-size; } .navigation__menu-102 { container: menu / inline-size; } @container navigation (inline-size >= 45ch) { .navigation__brand-102 span { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container menu (inline-size >= 60ch) { .navigation__menu-102 button { display: none; } .navigation__menu-102 ul { display: flex; } } </style> <div class="demo"> <div class="demo--content"> <nav class="navigation-102"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="navigation__brand-102"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" width="32"> <mask id="a" style="mask-type: luminance" maskUnits="userSpaceOnUse" x="1" y="1" width="30" height="30"> <path d="M16 2.7a13.3 13.3 0 1 0 0 26.6V2.7Z" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16 2.7a13.3 13.3 0 1 1 0 26.6V2.7Z" fill="#555" stroke="#fff" stroke-width="2.7" stroke-linejoin="round"></path> <path d="M16 24H6m10-5.3H3.3M16 13.3H3.3M16 8H6" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> </mask> <g mask="url(#a)"><path d="M0 0h32v32H0V0Z" fill="#766C7A"></path></g> </svg> <span>Jaberwocky</span> </a> <div class="navigation__menu-102"> <button type="button" class="btn-102 btn--small-102 btn--inverse-102" aria-expanded="false" aria-controls="#menu">Menu</button> <ul id="menu" role="list"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">Features</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Pricing</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">About</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Contact</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Blog</a></li> </ul> </div> <div class="navigation__actions-102"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Login</a> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="btn-102 btn--small-102">Sign Up</a> </div> </nav> </div> </div> <blockquote> <p>Given the demo size constraints, you may not see the list until you resize the demo container larger.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="improve-scalability-with-quantity-and-style-queries">Improve Scalability With Quantity and Style Queries</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#improve-scalability-with-quantity-and-style-queries" aria-labelledby="improve-scalability-with-quantity-and-style-queries"><span hidden="">#</span></a></div> <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> <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> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</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 class="token punctuation">}</span></code></pre> <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> <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> <span class="token comment">/* Styles when the container size is between 50-80ch */</span> <span class="token punctuation">}</span></code></pre> <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> <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> <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> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <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> <details false=""> <summary>CSS for "Navigation Quantity &amp; Style Queries"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.navigation__menu:has(:nth-child(6))</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 class="token punctuation">}</span> <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> <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> <span class="token selector">.navigation__menu button</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.navigation__menu ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .btn-351 { font-size: 1rem; text-decoration: none; font-family: inherit; cursor: pointer; align-self: start; justify-self: start; border: 2px solid currentColor; font-weight: 600; letter-spacing: 0.04em; transition: background-color 180ms ease-in-out; color: var(--button-color, var(--demo-primary)) !important; background-color: var(--button-bg, var(--demo-accent)); } .btn-351 { color: var(--button-color, var(--demo-primary)); background-color: var(--button-bg, var(--demo-accent)); font-size: 1rem; } .btn-351:where([aria-expanded], :has(.icon-351)) { display: flex; gap: 0.5em; align-items: center; } .btn-351:where(:not(:has(.icon-351))) { text-align: center; min-inline-size: 10ch; } .btn-351:where(:not(:has(.inclusively-hidden-351))) { padding: var(--button-padding, 0.35em 1em); border-radius: 0; } .btn--small-351 { --button-padding: 0.35em 0.75em; } .btn--inverse-351 { --button-bg: var(--demo-light); } .btn-351:is([aria-expanded])::after { content: ""; display: block; width: 1em; height: 0.5em; background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } .navigation-351 { margin-block: 1rem; padding: 1rem; background-color: var(--demo-light); box-shadow: 0 0.25rem 0.35rem -0.15rem rgba(166, 166, 166, 0.45); display: grid; grid-template-columns: auto 1fr auto; gap: 1rem; align-items: center; } .navigation-351 a:not(.btn-351) { text-decoration: none; padding: 0.15em 0.25em; width: fit-content; color: var(--demo-primary); border-bottom: 2px solid transparent; } .navigation-351 a:not(.btn-351):hover { background-color: var(--demo-accent); } .navigation-351 a:not(.btn-351)[aria-current] { border-bottom-color: var(--demo-accent--ui); } .navigation__brand-351 { display: inline-flex; align-items: baseline; gap: 0.25em; font-weight: bold; font-size: 1.25rem; transform: translateY(-0.1em); } .navigation__brand-351 svg { width: 1.25em; height: 1.25em; transform: translateY(0.15em); } .navigation__brand-351 span { /* Inclusively hidden so it is still read as a label by assistive tech */ height: 1px; overflow: hidden; position: absolute; clip-path: inset(50%); font-size: inherit; line-height: 0; } .navigation__menu-351 :is(button, a) { letter-spacing: 0.03em; font-weight: 600; color: var(--primary); } .navigation__menu-351 ul { gap: 0.5rem; } .btn-351:where([aria-expanded=false]) + ul { display: none; } .navigation__actions-351 { display: flex; gap: 0.5rem; align-items: center; } .navigation-351 { container: navigation / inline-size; } .navigation__menu-351 { container: menu / inline-size; } @container navigation (inline-size >= 45ch) { .navigation__brand-351 span { height: auto; overflow: unset; position: unset; clip-path: unset; } } @container menu (inline-size >= 60ch) { .navigation__menu-351 button { display: none; } .navigation__menu-351 ul { display: flex; } } .navigation__menu-351:has(:nth-child(6)) { --show-menu: true; } @container menu (40ch <= inline-size <= 60ch) { @container not style(--show-menu: true) { .navigation__menu-351 button { display: none; } .navigation__menu-351 ul { display: flex; } } } </style> <div class="demo"> <div class="demo--content"> <nav class="navigation-351"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="navigation__brand-351"> <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" width="32"> <mask id="a" style="mask-type: luminance" maskUnits="userSpaceOnUse" x="1" y="1" width="30" height="30"> <path d="M16 2.7a13.3 13.3 0 1 0 0 26.6V2.7Z" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M16 2.7a13.3 13.3 0 1 1 0 26.6V2.7Z" fill="#555" stroke="#fff" stroke-width="2.7" stroke-linejoin="round"></path> <path d="M16 24H6m10-5.3H3.3M16 13.3H3.3M16 8H6" stroke="#fff" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round"></path> </mask> <g mask="url(#a)"><path d="M0 0h32v32H0V0Z" fill="#766C7A"></path></g> </svg> <span>Jaberwocky</span> </a> <div class="navigation__menu-351"> <button type="button" class="btn-351 btn--small-351 btn--inverse-351" aria-expanded="false" aria-controls="#menu">Menu</button> <ul id="menu" role="list"> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" aria-current="page">Features</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Pricing</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">About</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Contact</a></li> <li><a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Blog</a></li> </ul> </div> <div class="navigation__actions-351"> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#">Login</a> <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#" class="btn-351 btn--small-351">Sign Up</a> </div> </nav> </div> </div> <div class="heading-wrapper h3"> <h3 id="container-queries-accessibility-and-fail-safe-resizing">Container Queries, Accessibility, and Fail-Safe Resizing</h3> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#container-queries-accessibility-and-fail-safe-resizing" aria-labelledby="container-queries-accessibility-and-fail-safe-resizing"><span hidden="">#</span></a></div> <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 &quot;reflow&quot; 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> <blockquote> <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> </blockquote> <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> <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> <video controls=""> <source src="https://moderncss.dev/img/posts/32/navigation-a11y.mp4" type="video/mp4" /> </video> <blockquote> <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 &quot;breakpoints.&quot;</p> </blockquote> <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> <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> <div class="heading-wrapper h2"> <h2 id="supporting-and-using-modern-css-features">Supporting and Using Modern CSS Features</h2> <a class="anchor" href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#supporting-and-using-modern-css-features" aria-labelledby="supporting-and-using-modern-css-features"><span hidden="">#</span></a></div> <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> <p>When evaluating whether a feature is &quot;safe to use&quot; 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 &quot;nice to haves&quot; 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> <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> <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> <blockquote> <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> </blockquote> <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> <hr /> <p>This material was originally presented at <a href="https://noti.st/st3ph/ea40FC/modern-css-for-dynamic-component-based-architecture">CSS Day 2023</a> and updated for <a href="https://frontenddesignconference.com/2024/">Front-end Design Conference 2024</a>.</p> </content>
</entry>
<entry>
<title>Testing Feature Support for Modern CSS</title>
<link href="https://moderncss.dev/testing-feature-support-for-modern-css/"/>
<updated>2023-05-01T00:00:00Z</updated>
<id>https://moderncss.dev/testing-feature-support-for-modern-css/</id>
<content type="html"><p>The pace of the CSS language can be challenging to keep up with! Browsers release features and fixes monthly, and the CSS Working Group is constantly working on specifications. So, how do you know using a new feature is &quot;safe&quot; to use? And what are the considerations around making that choice?</p> <p>Let's review how to:</p> <ul> <li>find information on new features</li> <li>test for support</li> <li>determine when to use a feature</li> <li>decide whether a fallback is needed</li> <li>use build tools and polyfills</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="finding-out-about-new-css-features">Finding Out About New CSS Features</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#finding-out-about-new-css-features" aria-labelledby="finding-out-about-new-css-features"><span hidden="">#</span></a></div> <p>Here is a list of ways you can find out about new and upcoming CSS features:</p> <ul> <li>following the developer relations folks from various browser makers, like <a href="https://twitter.com/Una">Una Kravets</a> and <a href="https://front-end.social/@jensimmons">Jen Simmons</a></li> <li>reviewing and starring issues you're interested in being added to CSS in <a href="https://github.com/w3c/csswg-drafts">the public GitHub</a></li> <li>subscribe to the CSS Working Group (CSSWG) blog feed</li> <li>check the release notes and feature blogs from browser engines <ul> <li><a href="https://webkit.org/">Webkit</a> (Safari)</li> <li><a href="https://www.mozilla.org/en-US/firefox/releases/">Firefox</a></li> <li><a href="https://developer.chrome.com/blog/">Chrome</a></li> </ul> </li> <li>consume materials from publications and individuals who focus a lot on CSS <ul> <li><a href="https://css-irl.info/">CSS IRL</a> by Michelle Barker</li> <li><a href="https://www.miriamsuzanne.com/">Miriam Suzanne</a></li> <li><a href="https://www.bram.us/">Bramus Van Damme</a></li> <li><a href="https://chenhuijing.com/">Chen Hui Jing</a></li> <li><a href="https://piccalil.li/">Andy Bell</a></li> <li><a href="https://www.kevinpowell.co/">Kevin Powell</a></li> </ul> </li> <li>subscribe to newsletters <ul> <li><a href="https://frontendfoc.us/">Frontend Focus</a></li> <li><a href="https://csslayout.news/">CSS Layout News</a></li> <li><a href="https://codepen.io/spark">The CodePen Spark</a></li> <li><a href="https://www.smashingmagazine.com/the-smashing-newsletter/">Smashing Magazine Newsletter</a></li> <li><a href="https://css-weekly.com/">CSS Weekly</a></li> </ul> </li> </ul> <p>Additionally, browser makers have started an annual effort to improve the interoperability of the web, which means striving to make features work consistently cross-browser. You can review the list and progress on those efforts on the <a href="https://wpt.fyi/interop-2023">Interop Dashboard</a>.</p> <p>As you absorb all that's possible in CSS now, remember: it's not about learning everything right now; it's about being aware of what's possible to help you develop a solution when needed!</p> <div class="heading-wrapper h2"> <h2 id="testing-for-css-support">Testing for CSS Support</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#testing-for-css-support" aria-labelledby="testing-for-css-support"><span hidden="">#</span></a></div> <p>Testing for CSS support - also called &quot;feature detection&quot; - can be done directly in your stylesheets using <code>@supports</code>.</p> <p>This at-rule allows testing:</p> <ul> <li>properties</li> <li>values</li> <li>selectors</li> </ul> <p>Within <code>@supports</code>, the test condition will return positive if the browser understands the property and the value.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">accent-color</span><span class="token punctuation">:</span> red<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles when accent-color is supported */</span> <span class="token punctuation">}</span></code></pre> <p>You can also test for selectors such as <code>:is()</code>, <code>:where()</code>, <code>:focus-visible</code>, and more. When using the <code>selector</code> condition with a function like <code>:is()</code>, a value must also be provided to the selector.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">:is(a)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* styles when :is() is supported */</span> <span class="token punctuation">}</span></code></pre> <p>Like media queries, you can combine tests with <code>and</code> as well as <code>or</code>, and negate tests with <code>not</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">leading-trim</span><span class="token punctuation">:</span> both<span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token punctuation">(</span><span class="token property">text-box-trim</span><span class="token punctuation">:</span> both<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when either property is supported */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">scroll-timeline-name</span><span class="token punctuation">:</span> a<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when both properties are supported */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">:focus-visible</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Styles when :focus-visible is not supported */</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="limitations-of-supports">Limitations of <code>@supports</code></h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#limitations-of-supports" aria-labelledby="limitations-of-supports"><span hidden="">#</span></a></div> <p>A significant limitation of <code>@supports</code> is that it currently cannot test for at-rules, meaning it cannot detect support of <code>@container</code> (container queries), <code>@layer</code> (cascade layers), and others. This lack of detection is problematic because at-rules typically greatly impact how you write and structure your CSS.</p> <p>Additionally, there can be issues testing for partial implementations.</p> <p>As an example of failure for partial implementations, a recent addition to CSS is the <code>:has()</code> selector. Unfortunately, the implementation at the time of writing in Firefox 112 may return a false positive when testing relational selectors with <code>:has()</code> like <code>li:has(+ )</code>. This is false because the partial implementation only supports more direct selectors like <code>li:has(a)</code>.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* This should fail in Firefox 112 */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token function">selector</span><span class="token punctuation">(</span><span class="token selector-function-argument selector">li:has(+ *)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* It may not fail, so the body becomes red */</span> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* This rule does fail to apply */</span> <span class="token selector">li:has(+ *)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>When using <code>@supports</code>, be sure to test the outcome in multiple browsers to ensure your styles apply with the result you intended.</p> </blockquote> <p>Also be aware that testing your condition with <code>@supports</code> requires <code>@supports</code> itself to be supported! In other words, check the support of the feature you're testing for <em>and</em> <code>@supports</code> to ensure you're not creating a condition that wouldn't actually have the chance to fail due to <code>@supports</code> being ignored if it's unsupported.</p> <p>Don't miss the section on <a href="https://moderncss.dev/testing-feature-support-for-modern-css/#alternate-methods-of-css-feature-detection">alternate methods of CSS feature detection</a>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="deciding-on-using-a-new-feature">Deciding on Using a New Feature</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#deciding-on-using-a-new-feature" aria-labelledby="deciding-on-using-a-new-feature"><span hidden="">#</span></a></div> <p>The CSS language is growing because the web is complex, and our requirements are ever-changing. In addition, device proliferation and user needs drive a lot of change and improvements in the underlying browser engines.</p> <p>For example, it was thought that container queries would never be possible, but the availability of related features enabled their release to be cross-browser complete in February 2023.</p> <p>But when do you know it's the right time to start using a new feature? After all, while the browsers Chrome, Edge, and Firefox have been termed &quot;evergreen&quot; - meaning they can automatically update themselves - there's no guarantee that users will allow that update quickly, if at all. Safari can also update in a way decoupled from OS updates, but doing so is not actively pushed, and typically only advanced users will seek out the updates. As Eric Bailey wrote, <a href="https://css-tricks.com/evergreen-does-not-mean-immediately-available/">evergreen does not mean immediately available</a>.</p> <p>A popular resource to check for feature availability is <a href="https://caniuse.com/">caniuse.com</a>. It's a fantastic place to get an overview of when browser features are added and notes on partial implementations or known bugs. However, the percentage shown for support should be taken as one metric and used alongside your actual audience analytics.</p> <p>Depending on your location in the world, your industry, or your product's specific marketing, you may need to delay using a particular feature. Or, you might find positive signs that the majority of your audience would be able to see the latest and greatest!</p> <p>If you use VSCode, I also highly recommend <a href="https://marketplace.visualstudio.com/items?itemName=webhint.vscode-webhint">the webhint extension</a> which alerts you when you are writing a feature that may not be well supported. This saves a trip out to caniuse, as it also gives you the list of where the feature isn't supported. With that information, you can decide whether you need to create a support solution as you write your styles. This also helps in reducing bugs from appearing later in browsers you may not have tested (although you should test as much as you can!).</p> <p>The impact of the feature you're looking to integrate also weighs heavily in this decision. For example, some modern CSS features are &quot;nice to haves&quot; that provide an updated experience that's great when they work but also don't necessarily cause an interruption in the user experience when they fail.</p> <p>Some examples of low-impact features include:</p> <ul> <li><code>accent-color</code> - change the color of native form elements, including checkboxes and radio buttons</li> <li><code>::marker</code> - apply <a href="http://localhost:8081/totally-custom-list-styles/#upgrading-to-css-marker">custom list bullet or numeral styling</a> like changing the color</li> <li><code>overscroll-behavior</code> - prevent scroll chaining to the background page when the end of a scrollable container is reached</li> <li><code>scroll-margin</code> - able to add margin to the scroll position, useful for <a href="https://smolcss.dev/#smol-article-anchors">anchor targets</a></li> <li><code>text-underline-offset</code> - allows adjusting the distance between a text underline and the text</li> </ul> <p>Other features that impact layout structure, or are tied to providing a more accessible experience, may not be advised to use until you are confident in a high likelihood of support. As a quick measure, consider whether a user would be prevented in doing the tasks they need to do on your website if the modern feature fails.</p> <div class="heading-wrapper h3"> <h3 id="assigning-fallback-solutions">Assigning Fallback Solutions</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#assigning-fallback-solutions" aria-labelledby="assigning-fallback-solutions"><span hidden="">#</span></a></div> <p>Another way to reasonably use newer features is to include them alongside fallback solutions. A &quot;fallback&quot; is a solution that works well enough to retain a positive user experience when the ideal feature isn't supported.</p> <p>Fallbacks work for two reasons. First, because CSS fails silently - meaning it skips definitions it doesn't understand without breaking the whole stylesheet. Second, because of the &quot;C&quot; in CSS which is the cascade that uses the listed order of definitions as part of how the browser determines which definition to apply. The cascade rules say that - given equal specificity - the last-ordered definition that the browser understands will &quot;win&quot;.</p> <p>For example, <code>aspect-ratio</code> is an awesome feature that I enjoy using to create uniform-sized images within a grid of cards or an image gallery. A fallback may provide a height for the images so that at least they are constrained in the layout, even if the ideal <code>aspect-ratio</code> isn't used.</p> <p>The following example is from my resource SmolCSS and the &quot;<a href="https://smolcss.dev/#smol-aspect-ratio-gallery">Smol Aspect Ratio Gallery</a>&quot; demo.</p> <p>First, we assume no support and give an explicit height. Then, using <code>@supports</code> to check for <code>aspect-ratio</code> support, we remove that explicit height and then use <code>aspect-ratio</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.smol-aspect-ratio-gallery li</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>25vh<span class="token punctuation">,</span> 15rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.smol-aspect-ratio-gallery li</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--aspect-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Often fallbacks can be a one-line alternative that uses an older syntax or method. These solutions are placed just before the ideal solution, which allows the modern solution to be used where supported. And when it's not supported, the last-ordered definition that is supported will be used, which we noted earlier was due to the cascade.</p> <p>In this example, our fallback uses the well supported <code>height</code> property with <code>100vh</code>. Then, we upgrade it to use the logical property of <code>block-size</code> with <code>100dvh</code>, where <code>dvb</code> is the &quot;<a href="https://12daysofweb.dev/2022/new-viewport-units/">dynamic viewport unit</a>&quot; that is better suited for environments like iOS Safari.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Fallback */</span> <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token comment">/* Ideal, modern version */</span> <span class="token property">block-size</span><span class="token punctuation">:</span> 100dvb<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="handling-prefixed-properties">Handling Prefixed Properties</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#handling-prefixed-properties" aria-labelledby="handling-prefixed-properties"><span hidden="">#</span></a></div> <p>Sometimes, lack of support is due to one browser adopting a proprietary version of a property. When this happens, they typically use a &quot;prefix&quot;. This is how we get properties such as <code>-webkit-background-clip</code>.</p> <p>A tricky part of working with prefixed properties is that sometimes other browsers enable them to work, but they remain prefixed due to a lack of official spec support. For some properties, they eventually get spec support, leading to browsers deprecating the prefixed version. And sometimes, one browser uses a prefixed version, and the others don't!</p> <p>Luckily, a tool exists to help manage prefixing properties. Autoprefixer is available as a PostCSS plugin (which we'll discuss later) and <a href="https://autoprefixer.github.io/">as a web app</a>.</p> <p>For example, one of my favorite techniques for controlling width without affecting the display property is to use <code>width: fit-content</code>. For the best support, it needs to include prefixed versions. Rather than remembering that, I can either include Autoprefixer in my build process or use the Autoprefixer web app to get the rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.example</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> -webkit-fit-content<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> -moz-fit-content<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You'll want to check <a href="https://caniuse.com/mdn-css_properties_width_fit-content">caniuse.com</a> or the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/fit-content#browser_compatibility">browser compatibility</a> section on MDN docs to be sure that a prefixed property you want to use has support cross-browser.</p> <div class="heading-wrapper h2"> <h2 id="alternate-methods-of-css-feature-detection">Alternate Methods of CSS Feature Detection</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#alternate-methods-of-css-feature-detection" aria-labelledby="alternate-methods-of-css-feature-detection"><span hidden="">#</span></a></div> <p>Sometimes you may wish to detect features like at-rules which <code>@supports</code> is unable to do. Or, you need more precise detection for partial implementations.</p> <p>CSS at-rules are exposed as a web API that is consumable by JavaScript. This means you can check for support using JavaScript and then apply classes or other modifications to indicate to your styles that a feature is available.</p> <p>For example, you can check for support of cascade layers with the following:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>CSSLayerBlockRule<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Cascade layers are supported</span> <span class="token punctuation">}</span></code></pre> <p>A web API function that works just like <code>@supports</code> is also available, which is <code>CSS.supports()</code>. This function accepts a value identical to what you would pass to the corresponding <code>@supports</code> block, including testing for selectors and the ability to combine or negate tests.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">'width: 1cqi'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Container query units are supported</span> <span class="token punctuation">}</span></code></pre> <p>When I was a young sprout coming up in web development, a popular solution for feature detection was <a href="https://modernizr.com/">Modernizr</a>. This was JavaScript that did feature tests and then added classes to the <code>&lt;html&gt;</code> element to indicate support or lack thereof. It was tremendously popular and even included in the official <a href="https://html5boilerplate.com/">HTML5 boilerplate</a>. But now, this solution is outdated, and I wouldn't recommend using it for new projects. This is because many of the tests likely aren't necessary for your audience anymore and because it hasn't been updated to include many of the very latest modern CSS features.</p> <p>However, I appreciate the ease of use of those support classes. They offload the effort of devising the right test for <code>@supports</code>, and can simplify creating selectors.</p> <p>I've created <a href="https://supportscss.dev/">SupportsCSS</a> as a feature detection solution that tests support of at-rules, selectors, and other features and applies classes to <code>&lt;html&gt;</code> with the results. The tiny script is also customizable so that it only tests for the features you care to include.</p> <p>Here's a summary of what SupportsCSS does:</p> <ul> <li>Checks for selectors like <code>:has()</code>, properties like <code>text-box-trim</code>, features like relative color syntax, and at-rules like <code>@layer</code></li> <li>Allows adding custom tests</li> <li>Exposes a results object to iterate over detected support, as well as individual results for quick conditional checks in JS</li> </ul> <p>Since the classes rely on JavaScript loading and succeeding, you will want to treat any styles based on the support classes as progressive enhancements. This is not too different from directly including <code>@supports</code> in your styles.</p> <p>However, if you have more critical styles and you <em>do</em> expect that <em>most</em> of your audience will have support, consider using a regular <code>@supports</code> block in your stylesheets. Then the styles are available as soon as your stylesheet is loaded.</p> <p>That said, you may like to review the test suite, which exposes the tests used for the features. You can copy any of the tests from the <a href="https://supportscss.dev/#test-suite">SupportsCSS test suite</a> that use <code>CSS.supports</code> and use those within <code>@supports</code>.</p> <div class="heading-wrapper h2"> <h2 id="using-build-tools-and-polyfills">Using Build Tools and Polyfills</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#using-build-tools-and-polyfills" aria-labelledby="using-build-tools-and-polyfills"><span hidden="">#</span></a></div> <p>Using <code>@supports</code> and JavaScript-based detection either directly or via <a href="https://supportscss.dev/">SupportsCSS</a> only tells you if a feature is supported. You are responsible for providing the experience for supported and unsupported features.</p> <p>Let's review polyfills and build tools that help bridge the gap while features are gaining support.</p> <div class="heading-wrapper h3"> <h3 id="polyfills">Polyfills</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#polyfills" aria-labelledby="polyfills"><span hidden="">#</span></a></div> <p>Sometimes, supporting a CSS feature is best done by including a polyfill. A polyfill is a script that enables a feature to work on an unsupported browser by creating a solution with other, better-supported features. Polyfills are used when a more simple fallback solution isn't possible or too complex to do manually.</p> <p>An example of a <a href="https://github.com/GoogleChromeLabs/container-query-polyfill">polyfill is for container queries</a>, which extends support clear back to Firefox 69, Chrome 79, Edge 79, and Safari 13.4. As with most polyfills, it has limitations and so doesn't provide full coverage of all the ways you may enact container query styles.</p> <p>Polyfills are a wonderfully helpful way to begin using &quot;future CSS&quot; today! Just be aware of their limitations. Additionally, polyfills may not keep up with syntax changes, leading to breaking a previously working implementation. You are responsible for keeping the polyfill version you include up-to-date.</p> <div class="heading-wrapper h3"> <h3 id="build-tools">Build Tools</h3> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#build-tools" aria-labelledby="build-tools"><span hidden="">#</span></a></div> <p>We briefly mentioned Autoprefixer, which is available as a web app or PostCSS plugin. But what is <a href="https://postcss.org/">PostCSS</a>? Well, it's a tool you use alongside a build tool like Gulp, Grunt, or Webpack. Through the use of PostCSS plugins, various features become available.</p> <p>A popular PostCSS plugin is <a href="https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env">postcss-preset-env</a> which &quot;allows you to use future CSS features today.&quot; It comes coupled with Autoprefixer. When using it, polyfills are added when needed, and additional plugins related to the features you're writing are applied.</p> <p>Several tools, like PostCSS, determine how to include feature support by using the <code>browserslist</code> entry in <code>package.json</code> or by including that information in the tool's configuration. Browserslist is a way of defining which browsers your application will support, which you can visualize and adjust using the <a href="https://browsersl.ist/#q=%3E+0.2%25+and+not+dead">Browserslist web app</a>.</p> <p>Besides polyfills, transpiling is another way build tools enable support of future CSS. Transpiling means rewriting the future version to a comparable but older and better-supported version. An example would be using the logical property <code>margin-inline: auto</code> would be transpiled to <code>margin-left: auto; margin-right: auto</code> if the browserslist targets didn't have full support. This allows writing your stylesheets with newer features, which over time your build tool will stop transpiling as support improves.</p> <p>Another option besides PostCSS that I've started using as my build tool of choice is LightningCSS. It includes Autoprefixer, minification, and transpiling of new CSS. I like it because it's a single package to include and replaces the individual includes I previously had for Autoprefixer and minification. In addition, I've found that I can use it to <a href="https://thinkdobecreate.com/articles/is-it-time-to-replace-sass/">replace Sass</a> for my more simple projects since it enables nesting and still lets me organize my styles into separate files.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional Resources</h2> <a class="anchor" href="https://moderncss.dev/testing-feature-support-for-modern-css/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <p>I encourage you to continue learning about this topic until you are comfortable with what it means to handle modern CSS support. It's fun to experiment and practice using modern CSS, but imperitive to consider what that means for your users.</p> <p>Here are a few other resources:</p> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Conditional_Rules/Using_Feature_Queries">Using feature queries</a> from MDN</li> <li><a href="https://matthiasott.com/notes/detecting-css-selector-support-with-javascript">Detecting CSS Selector Support with Javascript</a> by Matthias Ott</li> <li><a href="https://css-irl.info/detecting-css-selector-support/">Detecting CSS Selector Support</a> by Michelle Barker</li> <li><a href="https://www.smashingmagazine.com/2019/02/css-browser-support/">A Guide to CSS SUpport in Browsers</a> by Rachel Andrew</li> <li><a href="https://css-tricks.com/how-supports-works/">How @supports Works</a> by Chris Coyier</li> <li><a href="https://css-tricks.com/evergreen-does-not-mean-immediately-available/">&quot;Evergreen&quot; Does Not Mean Immediately Available</a> by Eric Bailey</li> <li>Related topic: <a href="https://www.smashingmagazine.com/2021/10/guide-debugging-css/">A Guide to CSS Debugging</a></li> </ul> </content>
</entry>
<entry>
<title>Container Query Units and Fluid Typography</title>
<link href="https://moderncss.dev/container-query-units-and-fluid-typography/"/>
<updated>2023-04-18T00:00:00Z</updated>
<id>https://moderncss.dev/container-query-units-and-fluid-typography/</id>
<content type="html"><p>Fluid typography is the term for designing <code>font-size</code> rules that responsively adapt the size based on the amount of available inline space. Before the availability of container query units, techniques usually relied on the viewport width - <code>vw</code> - unit. The viewport method is excellent for main page type, such as article headlines. However, viewport-based fluid typography doesn't quite work for narrower spaces that flex independently of the viewport, such as a grid of cards.</p> <p>We'll explore three ways to create dynamic fluid typography rules by leveraging container query units and CSS custom properties. You'll learn more about:</p> <ul> <li>creating mixins using custom properties</li> <li><code>max()</code>, <code>min()</code>, <code>calc()</code> and <code>clamp()</code></li> <li>container queries and units</li> <li><code>:is()</code> and <code>:where()</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Previously here on ModernCSS, I presented a method that relied on using Sass to perform some calculations and produce the rules to apply <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">viewport-based fluid typography</a>. You may still be interested in some of the other information including tips on preventing text-overflow and a few other considerations for web typography.</p> <p>However, not only can we upgrade the solution presented previously by dropping Sass, but the final rules will be far more resilient and context-independent.</p> <div class="heading-wrapper h2"> <h2 id="fluid-typography-basics-with-clamp">Fluid Typography Basics with <code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#fluid-typography-basics-with-clamp" aria-labelledby="fluid-typography-basics-with-clamp"><span hidden="">#</span></a></div> <p>The standard viewport-based fluid typography relies on the <code>clamp()</code> function and the <code>vw</code> (viewport width) unit.</p> <p>The <code>clamp()</code> function is one of several handy <a href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/">CSS math functions</a> and accepts three values: a minimum value, an ideal value, and a maximum value. The core idea of fluid typography is that the &quot;ideal&quot; value uses a dynamic unit - <code>vw</code> - in order to interpolate between the min and max. This effectively allows the font to resize along a preferred range.</p> <p>In the demo, the minimum allowed size is <code>1rem</code> and the maximum allowed size is <code>3rem</code>, where <code>4vw</code> allows interpolating along the range.</p> <details open=""> <summary>CSS for "Viewport-based fluid typography"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4vw + 1rem<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fluid-type-459 { font-size: clamp(1rem, 4vw + 1rem, 3rem); } </style> <div class="demo"> <div class="demo--content"> <p class="fluid-type-459">Viewport-based fluid typography.</p> </div> </div> <blockquote> <p>We'll talk more about that addition of <code>1rem +</code> shortly, but suffice it to say that it enables improved resizing when using display or text zoom, an important accessibility consideration.</p> </blockquote> <p>Now although that demo has a resize handle, you won't see any resizing of the font actually occur. That's because it's reliant on the width of your viewport, so you will need to resize your entire browser width to see a change. Already, we've demonstrated the problem with this technique!</p> <p>Due to this issue, a past remedy may have been to create component-specific styles that anticipate different viewport sizes and assign various font sizes within media queries. But now, with the availability of container queries, we can do better!</p> <div class="heading-wrapper h2"> <h2 id="quick-overview-of-container-queries">Quick Overview of Container Queries</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#quick-overview-of-container-queries" aria-labelledby="quick-overview-of-container-queries"><span hidden="">#</span></a></div> <p>I've previously written both a <a href="https://12daysofweb.dev/2021/container-queries/">condensed tutorial on container queries</a> and an <a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">in-depth primer on container queries</a>.</p> <p>What you need to know to understand the examples in this tutorial is that container queries allow defining rules for elements to respond to their ancestor container's available space. This is different from media queries which can only be based on the viewport.</p> <blockquote> <p>Technically this definition and the use in this tutorial is in consideration of container <em>size</em> queries. The spec also includes <em>style</em> queries for updating rules based on style features.</p> </blockquote> <p>The primary benefit of container queries is in creating more contextually appropriate layout rules that adapt to the true available space. With viewport media queries, rules are effectively orchestrated at the macro page level. But container queries allow responding to layout changes of micro elements and components as their context shifts through variable placement in page layouts.</p> <p>Container elements must be explicitly defined, which at the base level is done through the <code>container-type</code> property. For queries against available inline space, we use the value <code>inline-size</code>. Then, child elements of the container can query the container for its size with the <code>@container</code> rule and apply styles when that size condition is met.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span>inline-size > 300px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.container .child</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>The use of the term &quot;inline&quot; rather than &quot;width&quot; is from the precedent set by <a href="https://ishadeed.com/article/css-logical-properties/">logical properties</a>, which have their orientation adjusted based on the writing mode: right to left (RTL), left to right (LTR), or vertical. Using &quot;inline&quot; refers to the horizontal dimension for the writing mode.</p> <p>As we build up the solutions, we'll learn more about working with containment.</p> <div class="heading-wrapper h3"> <h3 id="browser-support">Browser Support</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#browser-support" aria-labelledby="browser-support"><span hidden="">#</span></a></div> <p><a href="https://caniuse.com/?search=container">Container size queries and units</a> are supported from Chromium 105, Safari 16, and Firefox 110.</p> <div class="heading-wrapper h2"> <h2 id="container-query-units">Container Query Units</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#container-query-units" aria-labelledby="container-query-units"><span hidden="">#</span></a></div> <p>Officially, these are called &quot;<a href="https://www.w3.org/TR/css-contain-3/#container-lengths">container query length units</a>,&quot; and they are a measure of the size of a containing element.</p> <p>Just as <code>1vw</code> equals <code>1%</code> of the viewport width, so does <code>1cqi</code> equal <code>1%</code> of a container's inline size. We'll be using <code>cqi</code> for purposes of defining fluid typography since we want the size to be associated with the horizontal axis of the writing mode.</p> <p>Interestingly, the CSS working group resolved that all elements would default to style containment. This means that the use of a container unit will work even without an ancestor that has containment. Keep reading to learn about a quirk of this behavior.</p> <div class="heading-wrapper h2"> <h2 id="setup-custom-properties">Setup Custom Properties</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#setup-custom-properties" aria-labelledby="setup-custom-properties"><span hidden="">#</span></a></div> <p>To begin our solutions, we'll set up some custom properties. This is because all of our rules will be designed to work with a sort of &quot;mixin&quot; rule that will intake the cascaded values from the custom properties.</p> <p>I'm referring to it as a mixin since it will be a general rule that takes the custom properties, applies a function, and produces variable results based on the custom property values. For best results, we'll work with the cascade to more predictably inherit values. This means we'll assign our base custom property values first and the mixin rules later in the stylesheet order.</p> <p>The starting structure for our rules involves choosing explicit font sizes for headline levels 1-4, which are the main focus of our base rules. We'll create custom properties for them and explicitly assign them per headline level.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--headline-1</span><span class="token punctuation">:</span> 2.75rem<span class="token punctuation">;</span> <span class="token property">--headline-2</span><span class="token punctuation">:</span> 2.35rem<span class="token punctuation">;</span> <span class="token property">--headline-3</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--headline-4</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Note that each rule updates the <code>--font-size</code> custom property to associate it with the size for that level. That's important because it enables the mixin rules we'll be creating, which will be generalized to scale for each of the input properties. Without generalizing to a mixin, we would have to repeat the function from the mixin within each separate headline rule.</p> <div class="heading-wrapper h3"> <h3 id="mixin-selector">Mixin Selector</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-selector" aria-labelledby="mixin-selector"><span hidden="">#</span></a></div> <p>So, what does this mixin actually look like? Well, the contents will be unique per each of our three solutions. However, the selector will be the same.</p> <p>We'll attach it to each heading element as well as the added heading classes and a utility class as well of <code>.fluid-type</code>. The utility class will allow using the mixin ad-hoc for type that may not be styled as a heading.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token comment">/* unique per solution */</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token property">margin-block-end</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>For other options to assign <code>line-height</code> that you may feel better fit your final fluid type solution, review my project <a href="https://css-typography-line-height.netlify.app/">CSS Typography Line Height</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="why-use-supports">Why use <code>@supports</code>?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports" aria-labelledby="why-use-supports"><span hidden="">#</span></a></div> <p>Due to a quirk of how custom properties work, we'll need to be explicit about separating our fluid sizes that use container query units from a fallback size. Otherwise, if a browser encounters the mixin and doesn't understand the container query unit, it will throw out the custom property value.</p> <p>The &quot;quirk&quot; is that it will then render the &quot;initial&quot; value of <code>1rem</code> instead of using a previously defined size for the element. The outcome is that all type will appear at <code>1rem</code>, removing any type size hierarchy from your application. That's why we need the explicit <code>@supports</code> check for whether the browser will understand the container query units before trying to apply the rule.</p> <p>To counter this behavior, you may assign a static value to the base headline rules, <em>or</em> include a solution prior to the container queries mixin that uses viewport units. Either fallback should be placed prior to and outside of the <code>@supports</code> condition.</p> <p><a href="https://moderncss.dev/container-query-units-and-fluid-typography/#cross-browser-fluid-type">One method</a> is shown after the first mixin.</p> <div class="heading-wrapper h3"> <h3 id="why-use-is-for-the-selector">Why use <code>:is()</code> for the selector?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-is-for-the-selector" aria-labelledby="why-use-is-for-the-selector"><span hidden="">#</span></a></div> <p>Two reasons:</p> <ol> <li>The more important reason is that it will raise the specificity of the entire rule to a class, which will make it more resilient to accidental overrides from inheritance, but it can also be matched or exceeded easily by later class-based or compound rules.</li> <li>The less important reason is to simplify the selector into a single-line list vs. a traditional comma-separated selector broken into multiple lines.</li> </ol> <blockquote> <p>If you encounter issues with <code>:is()</code>, such as complexity creating overrides, or if you want the ability to override through inheritance, you can switch to <code>:where()</code>. Use of <code>:where()</code> lowers specificity to zero, meaning later rules that may be component or page specific will override it easily due to the cascade without having to match or exceed the specificity.</p> </blockquote> <p>Note that <code>:is()</code> computes to the highest specificity in the given selector list, which is why I mentioned this rule would have the specificity of a class. Based on your preference and whether the behavior of <code>:is()</code> or <code>:where()</code> is useful for your context, you can alternatively remove the wrapper and use a standard selector list without <code>:is()</code> or <code>:where()</code>.</p> <div class="heading-wrapper h2"> <h2 id="upgrade-from-vw-to-cqi">Upgrade From <code>vw</code> to <code>cqi</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#upgrade-from-vw-to-cqi" aria-labelledby="upgrade-from-vw-to-cqi"><span hidden="">#</span></a></div> <p>The cornerstone of all of our methods will be upgrading from <code>vw</code> to <code>cqi</code> as our dynamic unit of choice to enable fluid typography.</p> <p>A starting rule to do this really is just a swap of those values.</p> <details open=""> <summary>CSS for "Fluid typography using cqi"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4cqi<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fluid-type-419 { font-size: clamp(1rem, 4cqi, 3rem); } .sorry-419 { display: none; } @supports not (font-size: 1cqi) { .sorry-419 { display: block; font-weight: bold; margin-bottom: 2rem; color: firebrick; } } </style> <div class="demo"> <div class="demo--content"> <p class="sorry-419">Sorry, your browser doesn't support container query units! The demos will not be fluid.</p> <p class="fluid-type-419">Viewport-based fluid typography.</p> </div> </div> <p>But - wait a minute - it still isn't working on resize of the container. However, it is responding based on the viewport. What's going on?!</p> <p>The container queries spec includes a provision that <a href="https://www.w3.org/TR/css-contain-3/#container-queries">every element defaults to a style container</a>, which is why the use of <code>cqi</code> already enables fluid resizing. But, since we didn't define a container for our demo, the measurement is still against the closest ancestor with containment applied.</p> <p>This site doesn't have containment applied on any ancestor of the demo, so the fallback behavior of container query units is to use the &quot;<a href="https://www.w3.org/TR/css-contain-3/#container-lengths">small viewport size for that axis</a>.&quot; This means for our rule where we are querying the &quot;inline&quot; axis, the viewport width is used as the measure.</p> <p>In order to produce the effect we're really after, which is to have the font size respond to the parent container, we need to assign containment.</p> <details open=""> <summary>CSS for "Container-based fluid typography"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4cqi<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .ctr-703 { container-type: inline-size; } .fluid-type-703 { font-size: clamp(1rem, 4cqi, 3rem); } .sorry-703 { display: none; } @supports not (font-size: 1cqi) { .sorry-703 { display: block; font-weight: bold; margin-bottom: 2rem; color: firebrick; } } </style> <div class="demo"> <div class="demo--content"> <p class="sorry-703">Sorry, your browser doesn't support container query units! The demos will not be fluid.</p> <div class="ctr-703"> <p class="fluid-type-703">Viewport-based fluid typography.</p> </div> </div> </div> <p>In this update, we put a parent div with the <code>container</code> class around the paragraph. Now the paragraph with the <code>fluid-type</code> class is responsively sizing according to the demo's inline size.</p> <blockquote> <p>A key concept of container queries is that they respond to the nearest ancestor with containment. If you apply rules that use container query units and aren't seeing them respond as you expect, you may have to adjust the markup and add a rule to allow the elements to carry a container with them.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="container-units-and-text-zoom-resizing">Container Units and Text Zoom Resizing</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#container-units-and-text-zoom-resizing" aria-labelledby="container-units-and-text-zoom-resizing"><span hidden="">#</span></a></div> <p>For the example rule explaining viewport-based fluid type, I mentioned that the inclusion of <code>1rem</code> added to the <code>vw</code> value was important for text resizing. It's because viewport-based methods are prone to restricting the font size from growing until at least the 200% required by the Web Content Accessibility Guidelines (WCAG) <a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text">Success Criterion 1.4.4: Resize Text</a>.</p> <p>As <a href="https://yatil.net/blog/resize-text-reflow">clarified by Eric Eggert</a>, this rule means that the on-screen rendered pixel height of the text must eventually be able to resize up to 200% of its original height at normal (100%) zoom. That technically doesn't need to be reached by the time the browser or text zoom setting is set to 200%, so it's acceptable if it's reached by, say, 300% zoom.</p> <p>For viewport-based fluid methods, inclusion of a <code>rem</code> value helps prevent issues with the text resizing. Without it, zoom-based resizing with only <code>vw</code> is more likely to fail to increase or stall out on increasing until a very high zoom value.</p> <blockquote> <p>PS - if you're not sure why we're dealing in rems, check the <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#selecting-a-unit-for-font-size">explanation of rem vs other units</a> in the earlier article here on fluid type.</p> </blockquote> <p>An interesting feature of swapping to use <code>cqi</code> instead of <code>vw</code> is that by its very nature it will continue to increase as long as the container inline size increases during zoom. This holds true both for browser/display zoom and text zoom applied at the OS level. In my testing, as long as <code>rem</code> is still used as the anchoring unit for the <code>font-size</code> definition, increases to 200% or more are more consistently achievable than <code>vw</code> methods.</p> <p>You should always test your fluid type rules in as many ways as you can to ensure zoom behavior works as expected. This means varying zoom levels with the type in multiple contexts such as a responsive grid of cards, in a medium-width article, a full width container, and a narrow sidebar.</p> <div class="heading-wrapper h2"> <h2 id="mixin-1-dynamic-font-size-ranges-with-clamp">Mixin 1: Dynamic Font Size Ranges With <code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-1-dynamic-font-size-ranges-with-clamp" aria-labelledby="mixin-1-dynamic-font-size-ranges-with-clamp"><span hidden="">#</span></a></div> <p>Our goal is to make a mixin function, so we need to manage a few more considerations than the more static rule created in the last section.</p> <p>Let's begin the rule by plugging in our <code>--font-size</code> custom property that was previously set up. We'll also enable a <code>--font-size-fluid</code> property with a default of <code>5cqi</code>. Like the size property, this would allow updating the target size per heading level, if desired.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token comment">/* TODO: define a minimum size */</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you missed it, jump back to the explanation of <a href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports">why we're using <code>@supports</code></a>.</p> </blockquote> <p>The missing piece in our mixin function is a definition for the minimum size allowed within the range.</p> <p>One option is to assign a custom property to update per inherited rule like the other parts. But instead, let's see how we can make the value more dynamic.</p> <p>Within <code>clamp()</code>, we can perform additional math calculations, no wrapping <code>calc()</code> required!</p> <p>This update says that the minimum allowed size should be 30% smaller than the <code>--font-size</code>. Due to the mathematical order of operations, the multiplication part of the equation is computed before the subtraction.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We also slipped in one more custom property for <code>--font-size-diff</code> to enable customizing the percentage difference if needed. For example, very large font sizes might allow a greater reduction, such as <code>0.5</code>.</p> <p>This produces a very nice effect that is scalable across our heading level rules with just a few tweaks that take advantage of our additional custom properties. However, it is presently possible for the minimum size to shrink smaller than perhaps we'd like, and potentially smaller than the regular body copy.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Regular, unstyled text uses <code>1rem</code>, which is approximately <code>16px</code>, as a browser default when no zoom features are applied. We can ensure that the minimum is not less than <code>1rem</code> by comparing it to the result of the equation.</p> <p>The CSS <code>max()</code> function accepts multiple values, and the larger computed size - the &quot;max&quot; value - will be used. Therefore, by passing it <code>1rem</code> and the equation, if the computed reduction of the <code>--font-size</code> would be less than <code>1rem</code>, the browser will use <code>1rem</code> instead.</p> <p>Here's our final mixin rule with the addition of <code>max()</code>.</p> <details false=""> <summary>CSS for "Mixin for dynamic font size ranges"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--headline-1</span><span class="token punctuation">:</span> 2.75rem<span class="token punctuation">;</span> <span class="token property">--headline-2</span><span class="token punctuation">:</span> 2.35rem<span class="token punctuation">;</span> <span class="token property">--headline-3</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--headline-4</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4.5cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4.25cqi<span class="token punctuation">;</span> <span class="token property">--font-size-diff</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 4cqi<span class="token punctuation">;</span> <span class="token property">--font-size-diff</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--headline-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 5cqi<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-495 { container-type: inline-size; --headline-1: 2.75rem; --headline-2: 2.35rem; --headline-3: 1.5rem; --headline-4: 1.15rem; } :is(.h1-495, .h2-495, .h3-495, .h4-495 ) { font-weight: 500; } .h1-495 { --font-size: var(--headline-1); font-size: var(--headline-1); } .h2-495 { --font-size: var(--headline-2); --font-size-fluid: 4.5cqi; font-size: var(--headline-2); } .h3-495 { --font-size: var(--headline-3); --font-size-fluid: 4.25cqi; --font-size-diff: 0.2; font-size: var(--headline-3); } .h4-495 { --font-size: var(--headline-4); --font-size-fluid: 4cqi; --font-size-diff: 0.2; font-size: var(--headline-4); } @supports (font-size: 1cqi) { .h1-495, .h2-495, .h3-495, .h4-495 { font-size: clamp( max(1rem, var(--font-size) - var(--font-size) * var(--font-size-diff, 0.3)), var(--font-size-fluid, 5cqi), var(--font-size) ); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-495"> <p class="h1-495">The five boxing wizards jump quickly.</p> <p class="h2-495">The five boxing wizards jump quickly.</p> <p class="h3-495">The five boxing wizards jump quickly.</p> <p class="h4-495">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Later in mixin #3 we'll look at a way to smooth out the transition between sizes so that it occurs more in-sync.</p> <div class="heading-wrapper h3"> <h3 id="cross-browser-fluid-type">Cross-Browser Fluid Type</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#cross-browser-fluid-type" aria-labelledby="cross-browser-fluid-type"><span hidden="">#</span></a></div> <p>To alleviate the affects of the <a href="https://moderncss.dev/container-query-units-and-fluid-typography/#why-use-supports">custom properties quirk</a>, an alternative option would be to define the mixin using <code>vw</code> and then override it within <code>@supports</code>. You will not achieve identical results since the <code>font-size</code> will be relative to the viewport instead of individual containers, but it also allows you to have some measure of fluid type. Be sure to test cross-browser and adjust as needed!</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Adjust element `--font-size-fluid` overrides to use `vw` */</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3vw<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 5cqi<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Add element `--font-size-fluid` overrides here that use `cqi` */</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="mixin-2-grow-from-a-base-font-size-with-calc">Mixin 2: Grow From a Base Font Size with <code>calc()</code></h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-2-grow-from-a-base-font-size-with-calc" aria-labelledby="mixin-2-grow-from-a-base-font-size-with-calc"><span hidden="">#</span></a></div> <p>In the first mixin, our use of <code>clamp()</code> allowed us to define a range for the font sizes. This is beneficial especially if you feel there needs to be a maximum for how large text can grow.</p> <p>Alternatively, if there doesn't strictly need to be an upper bound for your font sizes, we can simply allow the size to grow from a minimum base size.</p> <p>Instead of using our previously defined <code>--font-size</code>, we'll swap to defining base values. These are intended to be the smallest size we would allow, because our mixin will add on to the base.</p> <p>Here, we have listed and associated one base size per heading level, but you may prefer using semantic names like 'title', 'subtitle', 'caption', etc. Then those are assigned to the <code>--font-base-size</code> shared property for each heading rule, which will be passed into the mixin.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--h1-base</span><span class="token punctuation">:</span> 1.75rem<span class="token punctuation">;</span> <span class="token property">--h2-base</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--h3-base</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token property">--h4-base</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>You may want to retain the previous <code>--font-size</code> values to continue using as a fallback in case the fluid mixin isn't compatible with the user's browser.</p> </blockquote> <p>This mixin is quite a bit simplified from version one. Using <code>calc()</code>, we have a single equation where from the starting point of <code>--font-size-base</code> we are adding <code>--font-size-fluid</code>, which defaults to <code>3cqi</code>.</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <details false=""> <summary>CSS for "Mixin for growth from a base size"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--h1-base</span><span class="token punctuation">:</span> 1.75rem<span class="token punctuation">;</span> <span class="token property">--h2-base</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">--h3-base</span><span class="token punctuation">:</span> 1.35rem<span class="token punctuation">;</span> <span class="token property">--h4-base</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h1-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2.5cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h2-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2.25cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h3-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size-base</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-fluid</span><span class="token punctuation">:</span> 2cqi<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--h4-base<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-29 { container-type: inline-size; --h1-base: 1.75rem; --h2-base: 1.5rem; --h3-base: 1.35rem; --h4-base: 1.15rem; } :is(.h1-29, .h2-29, .h3-29, .h4-29 ) { font-weight: 500; } .h1-29 { --font-size-base: var(--h1-base); font-size: var(--h1-base); } .h2-29 { --font-size-base: var(--h2-base); --font-size-fluid: 2.5cqi; font-size: var(--h2-base); } .h3-29 { --font-size-base: var(--h3-base); --font-size-fluid: 2.25cqi; font-size: var(--h3-base); } .h4-29 { --font-size-base: var(--h4-base); --font-size-fluid: 2cqi; font-size: var(--h4-base); } @supports (font-size: 1cqi) { .h1-29, .h2-29, .h3-29, .h4-29 { font-size: calc(var(--font-size-base) + var(--font-size-fluid, 3cqi)); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-29"> <p class="h1-29">The five boxing wizards jump quickly.</p> <p class="h2-29">The five boxing wizards jump quickly.</p> <p class="h3-29">The five boxing wizards jump quickly.</p> <p class="h4-29">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Using this mixin, you'll likely want to use reduced fluid values compared to the first mixin. That's because the risk of the solution so far is that font sizes can, in theory, infinitely grow based on how much inline space is available. Practically speaking, this may not cause a significant issue unless you already have a very large font base that has the potential to spread across a large inline area.</p> <p>If you do feel a maximum is eventually required, we can add one by wrapping the equation with the <code>min()</code> function and introducing a <code>--font-size-max</code> property.</p> <p>How does <code>min()</code> result in a maximum boundary? Because as the font-size grows, if the computed value tied to the <code>cqi</code> value would exceed the <code>--font-size-max</code>, that would result in <code>--font-size-max</code> being the &quot;minimum&quot; value between the options. In that way it effectively caps the growth.</p> <details open=""> <summary>CSS for "Mixin for growth until a max size"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.fluid-type</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-max<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-base<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--font-size-fluid<span class="token punctuation">,</span> 3cqi<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-350 { container-type: inline-size; } .fluid-type-350 { --font-size-base: 1.35rem; --font-size-max: 3rem; font-size: min(var(--font-size-max), calc(var(--font-size-base) + var(--font-size-fluid, 3vw))); line-height: 1.2; margin-bottom: 0.65em; } @supports (font-size: 1cqi) { .fluid-type-350 { --font-size-fluid: 3cqi; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-350"> <p class="fluid-type-350">The five boxing wizards jump quickly.</p> <p class="fluid-type-350" style="--font-size-base: 1.15rem; --font-size-max: 2.5rem; --font-size-fluid: 2.5cqi;">The five boxing wizards jump quickly.</p> </div> </div> </div> <p>Now, you could extend this solution and dynamically compute the max end of the range like we did for the minimum end of the range in the first mixin. That's the beauty of custom properties used with defaults - you can choose an initial method for the mixin, and accept an override, too!</p> <div class="heading-wrapper h2"> <h2 id="mixin-3-generate-styles-using-a-type-scale-ratio">Mixin 3: Generate Styles Using a Type Scale Ratio</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#mixin-3-generate-styles-using-a-type-scale-ratio" aria-labelledby="mixin-3-generate-styles-using-a-type-scale-ratio"><span hidden="">#</span></a></div> <p>As noted in the intro, <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">fluid type has already been discussed</a> here on ModernCSS. In that tutorial, the key idea was building up font sizes according to a type scale ratio, and was computed with Sass.</p> <p>We can now take what we've learned in the other mixins and produce a comparable solution, but this time with only custom properties and CSS math functions, instead of relying on Sass!</p> <p>The idea of the ratio is to produce a collection of font sizes that feel harmonious as a group. A ratio also abstracts away the need to define individual, static font sizes since it's used to dynamically generate the sizes.</p> <p>This mixin will be very similar to the first mixin, with the difference being in how we compute the actual font size.</p> <p>Once again, we need to set up the custom properties for our base rules. We'll define a <code>--type-ratio</code> property, and have used a <a href="https://typescale.com/">&quot;perfect fourth&quot; ratio</a> as a starting point.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Perfect Fourth */</span> <span class="token property">--type-ratio</span><span class="token punctuation">:</span> 1.33<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In order for the ratio to be applied correctly, we need to compound the font sizes. This means given a base size, we'll multiply it by the ratio. Then we'll take the result and multiply it by the ratio again for the next size level, and so on.</p> <p>In the former Sass solution, we took advantage of a loop to manage the compounding. But the translation to custom properties means we'll need to do this ahead of time, so we'll add the pre-computed sizes as additional global properties.</p> <p>Our &quot;base&quot; will be the size we plan to apply to the body text so that our smallest headline is at least the first multiple of our <code>--type-ratio</code> larger than that. In this case with the perfect fourth ratio, that makes <code>--font-size-4</code> equal <code>1.33rem</code>. Each successive level takes the previous <code>--font-size-[LEVEL]</code> result and compounds it by applying the <code>--type-ratio</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Body font size */</span> <span class="token property">--body-font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Compounded headlines sizes */</span> <span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-3</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-2</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-1</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we'll assign the sizes to each headline rule. Reminder that the <code>font-size</code> listed in these rules will be used as a fallback for browsers that do not yet support container queries and units.</p> <pre class="language-css"><code class="language-css"><span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The mixin is a very similar calculation as the method discussed in the first solution. However, we'll compute the minimum size ahead of time. This is so that we can create a smoother transition for the group by adding the minimum + <code>1cqi</code> for the middle, ideal <code>clamp()</code> value. Since we're adding the container query unit onto the minimum, we're using a smaller value than the first mixin. Experiment and see how changing it to even a decimal value like <code>0.5cqi</code> affects the rate of change!</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--_font-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span> + 1cqi<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Additionally, we kept our friend <code>max()</code> to ensure the minimum wasn't able to reduce below the <code>--body-font-size</code>.</p> <blockquote> <p><strong>Fun fact:</strong> The <code>--_font-min</code> property doesn't need a <code>calc()</code> wrapper because at the point at which we create it as a custom property it's a simple list of values. When it gets used in <code>clamp()</code>, then the browser uses that context to actually do the calculation with the provided operators for the equation.</p> </blockquote> <p>Be sure to resize this as in a supporting browser, and also compare the transition to mixin #1.</p> <details false=""> <summary>CSS for "Mixin for generating font sizes from a type ratio"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* Perfect Fourth */</span> <span class="token property">--type-ratio</span><span class="token punctuation">:</span> 1.33<span class="token punctuation">;</span> <span class="token comment">/* Body font size */</span> <span class="token property">--body-font-size</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Compounded headlines sizes */</span> <span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-3</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-2</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--font-size-1</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h2, .h2</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h3, .h3</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">h4, .h4</span> <span class="token punctuation">{</span> <span class="token property">--font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size-4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">font-size</span><span class="token punctuation">:</span> 1cqi<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">:is(h1, .h1, h2, .h2, h3, .h3, h4, .h4, .fluid-type)</span> <span class="token punctuation">{</span> <span class="token property">--_font-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--font-size-diff<span class="token punctuation">,</span> 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token function">max</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--_font-min<span class="token punctuation">)</span> + 1cqi<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-size<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-865 { container-type: inline-size; /* Perfect Fourth */ --type-ratio: 1.33; /* Body font size */ --body-font-size: 1rem; /* Compounded headlines sizes */ --font-size-4: calc(var(--body-font-size) * var(--type-ratio)); --font-size-3: calc(var(--font-size-4) * var(--type-ratio)); --font-size-2: calc(var(--font-size-3) * var(--type-ratio)); --font-size-1: calc(var(--font-size-2) * var(--type-ratio)); } :is(.h1-865, .h2-865, .h3-865, .h4-865 ) { font-weight: 500; } .h1-865 { --font-size: var(--font-size-1); font-size: var(--font-size); } .h2-865 { --font-size: var(--font-size-2); font-size: var(--font-size); } .h3-865 { --font-size: var(--font-size-3); font-size: var(--font-size); } .h4-865 { --font-size: var(--font-size-4); font-size: var(--font-size); } @supports (font-size: 1cqi) { .h1-865, .h2-865, .h3-865, .h4-865 { --_font-min: var(--font-size) - var(--font-size) * var(--font-size-diff, 0.3); font-size: clamp( max(var(--body-font-size), var(--_font-min)), var(--_font-min) + 1cqi, var(--font-size) ); line-height: 1.1; margin-bottom: 0.65em; } } </style> <div class="demo"> <div class="demo--content"> <div class="container-865"> <p class="h1-865">The five boxing wizards jump quickly.</p> <p class="h2-865">The five boxing wizards jump quickly.</p> <p class="h3-865">The five boxing wizards jump quickly.</p> <p class="h4-865">The five boxing wizards jump quickly.</p> <p>Regular body copy size for a paragraph and non-headline text. </p></div> </div> </div> <p>For best results, if you would like to change the <code>--font-size-diff</code> value, you'll likely want to change it as a global property. That's because changing it for individual levels will interfere with the ratio-based sizing.</p> <p>Additionally, you can try out increasing the base for the original calculation if you feel it's too close in size to the body copy. A quick way to do that is add it into the calculation for <code>--font-size-4</code>, such as:</p> <pre class="language-css"><code class="language-css"><span class="token property">--font-size-4</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--body-font-size<span class="token punctuation">)</span> + 0.25rem<span class="token punctuation">)</span> * <span class="token function">var</span><span class="token punctuation">(</span>--type-ratio<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <blockquote> <p>As a challenge to apply what you've learned, you could adapt the second mixin that grows from a base value to use type-ratio generated base values.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="tips-on-using-the-mixins">Tips on Using the Mixins</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#tips-on-using-the-mixins" aria-labelledby="tips-on-using-the-mixins"><span hidden="">#</span></a></div> <p>Practically speaking, when using any of the mixins presented in this tutorial you will possibly want to create containers out of elements like <code>&lt;article&gt;</code> or create a utility class to apply containment. And, where containment is applied will be something to consider when you define markup for components like cards. Otherwise, as we learned, it may seem as though the viewport is being used to compute the font size rather than your intended context.</p> <p>While our mixin rules are being applied broadly to headlines, you may prefer to <em>only</em> apply fluid type when a utility class is used. Or, you may determine a few variations that better fit your specific contexts and components, such as scales for articles, cards, forms, and tables.</p> <div class="heading-wrapper h3"> <h3 id="which-one-should-i-use">Which One Should I Use?</h3> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#which-one-should-i-use" aria-labelledby="which-one-should-i-use"><span hidden="">#</span></a></div> <p>Since all the mixins use the container query unit of <code>cqi</code> to trigger expanding and shrinking of the font size, your context and preferences will be the deciding factors.</p> <p>Perhaps you feel expanding from a base is easier to reason about, or produces the results you're after more consistently for a particular component, so you use mixin number two. Or, maybe you like defining the ranges more precisely, or have been given those ranges in design specs, so mixin number one that uses <code>clamp()</code> better fits your style. And maybe you just prefer to leave the sizes up to math, so providing a type scale like mixin number three works best for you.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources-on-fluid-typography">Additional Resources on Fluid Typography</h2> <a class="anchor" href="https://moderncss.dev/container-query-units-and-fluid-typography/#additional-resources-on-fluid-typography" aria-labelledby="additional-resources-on-fluid-typography"><span hidden="">#</span></a></div> <p>Fluid type is far from a new topic, and the methods presented here are not the only ways to accomplish it! I've learned a lot from the following resources, and I encourage you to continue researching to find the technique that suits your preference or project best.</p> <p><strong>Most resources use viewport-based calculations</strong> since container query units are a more recent addition to the web platform. As such, they may need adapted if you prefer basing the sizing on containers.</p> <ul> <li>Skip any of the manual calculations and use values provided by <a href="https://utopia.fyi/type/calculator/">Utopia.fyi</a> (currently viewport-based only)</li> <li>A thorough <a href="https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/">review of fluid typography with clamp()</a> by Adrian Bece</li> <li>An alternative to the mixins here is this solution from Andy Bell for <a href="https://archive.hankchizljaw.com/wrote/custom-property-controlled-fluid-type-sizing/">custom property controlled fluid type sizing</a></li> <li>Scott Kellum of Typetura presents this innovative method of <a href="https://css-tricks.com/intrinsic-typography-is-the-future-of-styling-text-on-the-web/">deriving font sizes using keyframe animation</a> to do the interpolation</li> </ul> </content>
</entry>
<entry>
<title>Contextual Spacing For Intrinsic Web Design</title>
<link href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/"/>
<updated>2022-05-03T00:00:00Z</updated>
<id>https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/</id>
<content type="html"><p>The user's browsing environment is not predictable. Tell other developers, and for goodness sakes, tell your designers. Let's learn how to coexist with that unpredictability by using adaptive, contextual spacing techniques.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>In 2018, Jen Simmons introduced the term &quot;Intrinsic Web Design&quot; in her talk &quot;<a href="https://talks.jensimmons.com/videos/h0XWcf">Everything You Know About Web Design Just Changed</a>.&quot; She also shared the principles of intrinsic web design that we'll use as guidance:</p> <ol> <li><strong>Contracting &amp; Expanding</strong> - the way we consider how our design will adapt to a change in available space</li> <li><strong>Flexibility</strong> - using primarily flexbox and grid in combination with newer units and functions in a way that enables our layouts to adapt at various rates to the available space</li> <li><strong>Viewport</strong> - the ability to use all four sides of the viewport as well as take advantage of viewport units</li> </ol> <blockquote> <p>Using adaptive layout techniques is a trust exercise between designers, developers, and the browser.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="properties-and-functions-for-intrinsic-design">Properties and functions for intrinsic design</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#properties-and-functions-for-intrinsic-design" aria-labelledby="properties-and-functions-for-intrinsic-design"><span hidden="">#</span></a></div> <p>Let's start with a review of the foundational essentials for creating intrinsicly sized elements.</p> <div class="heading-wrapper h3"> <h3 id="clamp">Clamp</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#clamp" aria-labelledby="clamp"><span hidden="">#</span></a></div> <p>A versatile CSS function that is key to intrinsic web design is <code>clamp()</code>, and it has had <a href="https://caniuse.com/css-math-functions">stable support since March 2020</a>.</p> <p>Clamp accepts three values: the minimum, ideal, and maximum values. This effectively lets you provide flexible constraints.</p> <p>The trick with <code>clamp()</code> is in that ideal value where a dynamic unit such as view width must be used to trigger the transition between the min and max.</p> <p>You may have encountered <code>clamp()</code> under the umbrella of fluid typography, which relies on viewport units. Here's a set of example values:</p> <pre class="language-css"><code class="language-css"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 4vw<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Based on the current computed value of 4vw, the <code>font-size</code> will adjust as the viewport grows and shrinks. But it will never be smaller than <code>1rem</code> or larger than <code>3rem</code>.</p> <p>We'll review more use cases for <code>clamp</code> later on for our spacing techniques.</p> <p>So how do we go about using <code>clamp()</code> for intrinsic design? My suggestion is:</p> <ul> <li><strong>designers</strong> provide the min and max values</li> <li><strong>devs</strong> determine dynamic dimensions</li> </ul> <p>The <em>min</em> and <em>max</em> values can be provided by design tokens, which may be a familiar concept if you come from design systems or are using frameworks that provide sizing ramps. I'll continue to call out opportunities for design tokens as we explore more techniques since they are an excellent method to map our constraints back to designs and wireframes.</p> <div class="heading-wrapper h3"> <h3 id="min-and-max-functions">Min and Max functions</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#min-and-max-functions" aria-labelledby="min-and-max-functions"><span hidden="">#</span></a></div> <p>The <code>min()</code> and <code>max()</code> functions enable us to provide context-dependent options. These have essentially the same level of support as <code>clamp()</code>.</p> <p>Both <code>min()</code> and <code>max()</code> accept two or more values. For the <code>min()</code> function, the browser will use the smallest computed value, and the inverse will happen for <code>max()</code> where the browser will use the largest computed value.</p> <p>Another feature of <code>min</code>, <code>max</code>, and <code>clamp</code> is that we can perform additional calculations without needing a nested <code>calc()</code> function. So for the following definition, we're asking the browser to choose the smallest value between <code>100vw - 3rem</code> and <code>80ch</code>.</p> <pre class="language-css"><code class="language-css"><span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span></code></pre> <p>This results in <code>100vw - 3rem</code> being selected when the viewport is &lt; <code>80ch</code>, and <code>80ch</code> being selected when the viewport is &gt; <code>80ch</code>.</p> <p>If we take that rule and add the logical property <code>margin-inline</code> set to <code>auto</code>, then we have a really modern container class.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We can take our container class a step further and set up an optional custom property of <code>container-max</code> that uses <code>80ch</code> as a fallback. So now we have a modern, ultra-flexible rule.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>100vw - 3rem<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--container-max<span class="token punctuation">,</span> 80ch<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-inline</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It can be a bit tricky to understand when to use <code>min</code> and <code>max</code>.</p> <p>Let's consider the following values for <code>max</code>.</p> <pre class="language-css"><code class="language-css"><span class="token function">max</span><span class="token punctuation">(</span>2rem<span class="token punctuation">,</span> 4vh<span class="token punctuation">)</span></code></pre> <p>When the computed value of <code>4vh</code> becomes less than <code>2rem</code>, the <code>max</code> function will select <code>2rem</code>.</p> <p>So effectively, what's happening is that the selected choice of <code>2rem</code> is the minimum value you will allow for this rule.</p> <p>Now let's flip to <code>min()</code>:</p> <pre class="language-css"><code class="language-css"><span class="token function">min</span><span class="token punctuation">(</span>100%<span class="token punctuation">,</span> 60ch<span class="token punctuation">)</span></code></pre> <p>This rule for <code>min()</code> means that 60ch is effectively the maximum allowed value for this element.</p> <p>If you're wondering why we'd use <code>min</code> and <code>max</code> instead of listing out separate corresponding properties, it's because we can use them anywhere a numeric value is allowed, not just for dimensions.</p> <p>For example, <code>background-size</code> like we reviewed in <a href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/">practical uses of CSS math functions</a> where we explored more about <code>clamp()</code>, <code>min()</code>, and <code>max()</code>.</p> <div class="promo"><p><em>Hey there!</em> Registration is open for my July workshop "Level-Up With Modern CSS" &mdash; <a href="https://smashingconf.com/online-workshops/workshops/stephanie-eckles-july/"><strong>register today!</strong></a></p></div> <div class="heading-wrapper h3"> <h3 id="fitminmax-content">Fit/Min/Max-Content</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#fitminmax-content" aria-labelledby="fitminmax-content"><span hidden="">#</span></a></div> <p>Next up, we have <code>fit-content</code>, <code>min-content</code>, and <code>max-content</code>, which allow intrinsic sizing.</p> <p><a href="https://caniuse.com/intrinsic-width">Support for these keywords</a> is best when paired with the <code>width</code> property, and you may find the need to use prefixes depending on your audience.</p> <p>Let's compare how these sizing keywords render for text content when applied to the <code>width</code> property:</p> <details open=""> <summary>CSS for "Intrinsic Keywords Comparison"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.fit-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> fit-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.min-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> min-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.max-content</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> max-content<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .fit-content-675 { width: fit-content; } .min-content-675 { width: min-content; } .max-content-675 { width: max-content; } .keywords-wrap-675 { padding: 1rem; background-color: thistle; } .keywords-675 { line-height: 1.1; background-color: rebeccapurple; color: white; font-size: 1.25rem; } .keywords-675 + .keywords-675 { margin-top: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="keywords-wrap-675"> <p class="keywords-675 fit-content-675">width set to fit-content</p> <p class="keywords-675 min-content-675">width set to min-content</p> <p class="keywords-675 max-content-675">width set to max-content</p> </div> </div> </div> <ul> <li><code>fit-content</code> grows just large enough to contain its contents</li> <li><code>min-content</code> only grows large enough to match the width of the longest word and will apply soft-wrapping</li> <li><code>max-content</code> will continue growing as large as its contents require</li> </ul> <p>At first look, it can be hard to tell the difference between <code>fit-content</code> and <code>max-content</code>, so let's expand the text values:</p> <style> .fitt-content-37 { width: fit-content; } .maxx-content-37 { width: max-content; } .keywords-wrap-37 { padding: 1rem; background-color: thistle; max-width: 25ch; outline: -.5rem-37 solid dashed; } .keywords-37 { line-height: 1.1; background-color: rebeccapurple; color: white; font-size: 1.25rem; } .keywords-37 + .keywords-37 { margin-top: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="keywords-wrap-37"> <p class="keywords-37 fitt-content-37">fit-content will grow but not overflow</p> <p class="keywords-37 maxx-content-37">max-content has overflow potential</p> </div> </div> </div> <p>To be honest, I haven't found many use cases for <code>min-content</code> or <code>max-content</code>. But <code>fit-content</code> is a top-shelf property.</p> <p>In this demo, the &quot;alert&quot; has <code>width: fit-content</code>. Notice that it is only growing to the equivalent of its <code>max-content</code>, which is narrower than the available inline space.</p> <style> .wrap-843 { padding: 1rem; background-color: thistle; } .article-843 { padding: clamp(0.5rem, 3%, 1.5rem); width: min(100%, 60ch); margin-inline: auto; background-color: #fff; } .alert-843 { width: -moz-fit-content; width: fit-content; background-color: rebeccapurple; color: #fff; padding: 0.5rem .75rem; border-radius: 0.5rem; margin-block: 1rem; } .placeholder-843 { background-image: repeating-linear-gradient(to bottom, #B5B8B8 0 1rem, #fff 1rem 1.75rem); height: 4.5rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-843"> <div class="article-843"> <div class="placeholder-843"></div> <p class="alert-843">Lorem ipsum, dolor sit amet.</p> <div class="placeholder-843"></div> </div> </div> </div> </div> <p>The magic of <code>fit-content</code> is achieving content-relative width without changing the <code>display</code> value, meaning we can save the <code>display</code> property. And in this flow context, the alert element can continue to use block behavior which means margins continue to work.</p> <div class="heading-wrapper h3"> <h3 id="grid-units-and-functions">Grid Units and Functions</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#grid-units-and-functions" aria-labelledby="grid-units-and-functions"><span hidden="">#</span></a></div> <p>Folks - we've had <a href="https://caniuse.com/css-grid">CSS grid support</a> since spring 2017.</p> <blockquote> <p>CSS Grid is the perfect toolset for achieving flexibility within constraints.</p> </blockquote> <p>Under the topic of intrinsic web design, we will review my top two uses of grid.</p> <p>The following is the most magical CSS definition because it creates an intrinsically sized layout grid.</p> <pre class="language-css"><code class="language-css"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>30ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>I've written about it in several other Modern CSS articles, but here's the summary. Except for the <code>ch</code> unit, everything in that line is CSS grid-specific.</p> <ul> <li><code>repeat()</code> defines a recurring pattern to use for the specified grid tracks</li> <li><code>auto-fit</code> means to create as many tracks as will fit, according to the next part of the definition</li> <li><code>minmax()</code> is a CSS grid function that defines a range of values that will be used to compute each track size where the first value is the minimum and the second value is the maximum</li> </ul> <p>Here's the rule in action:</p> <details open=""> <summary>CSS for "intrinsic grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.grid-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <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>--grid-min<span class="token punctuation">,</span> 20ch<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-818 { padding: 1rem; background-color: thistle; } .grid-layout-818 { list-style: none; padding: 5%; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 20ch)), 1fr)); gap: 1rem; width: min(100%, 80ch); margin-inline: auto; } .grid-layout-818 li { font-size: 2rem; display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; background-color: rebeccapurple; color: white; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-818"> <ul class="grid-layout-818"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> </div> </div> </div> <p>At a larger size, three tracks are created, and as the inline space reduces, the track space also reduces. Once track size squeezes elements below the <code>30ch</code> minimum, elements drop to be added to or create a new row.</p> <p>In the demo, we also enhanced the rule to include a nested <code>min()</code> function with <code>100%</code> as one of the options. This reduces the chance of overflow by allowing the element to shrink below <code>30ch</code> when the space requires being more narrow. We also enabled this rule to scale by adding an optional custom property of <code>--grid-min</code> to control the &quot;breakpoint.&quot;</p> <p>In this next rule, we'll use the grid-only function version of <code>fit-content()</code>. Using the <code>fit-content()</code> function means we can provide our own preferred max value, as seen for the sidebar in the demo, which maxes out at <code>20ch</code>.</p> <details open=""> <summary>CSS for "fit-content() grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">fit-content</span><span class="token punctuation">(</span>20ch<span class="token punctuation">)</span> <span class="token function">minmax</span><span class="token punctuation">(</span>50%<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-368 { padding: 1rem; background-color: thistle; } .sidebar-layout-368 { display: grid; grid-template-columns: fit-content(20ch) minmax(50%, 1fr); gap: 2rem; width: min(100%, 80ch); margin-inline: auto; } .sidebar-layout-368 > * { display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; padding: 2vw; background-color: rgba(255, 255, 255, 0.15); } .sidebar-layout-368 .article-368 p { font-size: 2.25rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-368"> <div class="sidebar-layout-368"> <div class="sidebar"> Sidebar content goes here </div> <div class="article-368"> <p>Article</p> </div> </div> </div> </div> </div> <p>Altogether, the behavior produced by this rule is that the article element will reduce until it hits its 50% minimum. At this point, the sidebar will finally begin to compress until it reaches the equivalent of <code>min-content</code>.</p> <p>We can improve the flexibility of this rule by again including a custom property. I love this definition because it can accommodate other content types, like this image.</p> <details open=""> <summary>CSS for "fit-content() grid layout with image"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.sidebar-layout</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">fit-content</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--sidebar-max<span class="token punctuation">,</span> 20ch<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">minmax</span><span class="token punctuation">(</span>50%<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .wrap-763 { padding: 1rem; background-color: thistle; } .sidebar-layout-763 { display: grid; grid-template-columns: fit-content(var(--sidebar-max, 20ch)) minmax(50%, 1fr); gap: 2rem; width: min(100%, 80ch); margin-inline: auto; } .sidebar-layout-763 .article-763 { display: grid; place-content: center; aspect-ratio: 1; border: 2px solid #cc92ff; border-radius: 0.25rem; padding: 2vw; background-color: rgba(255, 255, 255, 0.15); } .sidebar-layout-763 .article-763 p { font-size: 2.25rem; } </style> <div class="demo"> <div class="demo--content"> <div class="wrap-763"> <div class="sidebar-layout-763"> <div class="sidebar"> <img src="https://assets.codepen.io/1101822/coffee.jpeg" /> </div> <div class="article-763"> <p>Article</p> </div> </div> </div> </div> </div> <p>Just keep in mind that the image doesn't have such a thing as a min-content value, so it will continue to shrink based on the remaining allotment of space.</p> <div class="heading-wrapper h2"> <h2 id="intrinsic-contextual-spacing">Intrinsic, contextual spacing</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#intrinsic-contextual-spacing" aria-labelledby="intrinsic-contextual-spacing"><span hidden="">#</span></a></div> <p>So far, what we've talked about is how to affect how elements take up space and size themselves. Now it's finally time to talk about affecting the spacing between and around elements which will help us get the most out of intrinsic web design.</p> <p>Despite the long-time availability of units besides the pixel, we continue to define spacing almost exclusively in pixels, maybe sometimes in rems.</p> <p>Pixels are the least flexible unit for creating intrinsic layouts. So what if we could provide the browser with a better rubric for determining sizes for spacing? And what if we could do it without being overbearing with media queries?</p> <p>The first thing to acknowledge regarding spacing is that the properties involved in spacing - gap, padding, and margin - have different purposes. So let's learn how to use more appropriate units and create adaptive methods for handling spacing between and around elements.</p> <div class="heading-wrapper h3"> <h3 id="padding">Padding</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#padding" aria-labelledby="padding"><span hidden="">#</span></a></div> <p>Padding is for handling individual box spacing. Our upgraded technique will use <code>clamp()</code> with percents and <code>rem</code>.</p> <p>A reminder that percents used with padding are calculated relative to the element's inline size, so for <code>clamp()</code>, we can use a percentage as an element-relative dynamic value.</p> <p>Here's a comparison of two mobile experiences - the left uses pixels to define margin and padding, and the right uses <code>clamp()</code>:</p> <p><img src="https://moderncss.dev/img/posts/29/clamp-card.png" alt="" /></p> <p>For the version using <code>clamp()</code>, notice the gains in inline space, which leads to a more comfortable reading experience.</p> <p>Pixels are often designed to work for a &quot;desktop&quot; or widescreen environment. And updating their values would mean creating a series of media-query controlled breakpoints. So instead, here's a demo of our improvement using <code>clamp()</code> and<code> min()</code>.</p> <p>We use the same padding rule for both the article element and the cards. The trick on the article is to toggle between <code>60ch</code>, which applies on large inline spaces, and <code>100%</code>, which results in no extra outside &quot;gutter&quot; space on narrow inline spaces.</p> <p>Another context impacted positively by these rules is the Web Content Accessibility Guidelines Success Criterion for reflow, which defines expectations for browser zoom up to 400%. At this point, the computed width of the screen is assumed to be around 320px. Unfortunately, we don't have a zoom media query, but any rules that affect a viewport approaching 320px will impact this context. So, the narrow layout will display at the high zoom and be allowed the more ideal line length for reading.</p> <blockquote> <p>Learn more about <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">reflow and other modern CSS upgrades to improve accessibility</a></p> </blockquote> <p>I recommend trying out creating padding custom properties.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--padding-sm</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 3%<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--padding-md</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6%<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--padding-lg</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>3rem<span class="token punctuation">,</span> 12%<span class="token punctuation">,</span> 6rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>That middle percent value may seem a bit magic - and, well, it is - but I've found a decent starting point is to double the maximum value to use as the percent. And hey, there are some more design token opportunities!</p> <div class="heading-wrapper h3"> <h3 id="margin">Margin</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#margin" aria-labelledby="margin"><span hidden="">#</span></a></div> <p>Next up is margin, and for our purposes, we'll be explicitly using it for block layout spacing, by which I mean vertical spacing.</p> <p>We'll use the <code>min()</code> function with viewport units and <code>rem</code> for margin. Viewport units will allow creating contextual spacing.</p> <p>Here is our baseline rule for margin;</p> <pre class="language-css"><code class="language-css"><span class="token selector">.block-flow</span> <span class="token punctuation">{</span> <span class="token property">margin-block-start</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>4rem<span class="token punctuation">,</span> 8vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>I'm borrowing the term &quot;flow&quot; from Andy Bell's flow rule and &quot;block&quot; because the rule is applied using the logical property of <code>margin-block-start</code>. If you're not yet familiar with logical properties, <code>margin-block-start</code> is the logical companion to <code>margin-top</code>.</p> <p>Within <code>min()</code>, we've supplied the values of <code>4rem</code> and <code>8vh</code>. So you can think of <code>4rem</code> as the static value and <code>8vh</code> as the dynamic value.</p> <p>Here's the rule's effect on a large vs. small context. Technically, the computed margin for these values only saves about <code>13px</code> on the &quot;mobile&quot; version, which may not seem too impactful.</p> <p><img src="https://moderncss.dev/img/posts/29/block-flow.png" alt="the .block-flow rule as viewed on a laptop vs. a mobile phone" /></p> <p>However, once again, in our zoom context, the difference made by enabling the <code>8vh</code> option is quite positive.</p> <p><img src="https://moderncss.dev/img/posts/29/block-flow-zoom.png" alt="" /></p> <p>In this context, it's desirable to reduce unnecessary space. This demo also emphasizes the difference between a zoom context and mobile: landscape orientation. In contrast, typically, we're most concerned about designing for portrait orientation in the mobile context.</p> <p>So here is a starting point for our block-flow custom properties, where once again, simply doubling the static <code>rem</code> value to produce the <code>vh</code> value works out pretty well. And as we explored, both true mobile and desktop zoom are the critical contexts to test out and verify the <code>vh</code> value. The static <code>rem</code> value is another design token opportunity.</p> <p>Since I need to stay on brand, here’s your ultra modern CSS rule to setup block flow.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:is(body, .block-flow) > * + *</span> <span class="token punctuation">{</span> <span class="token property">margin-block-start</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>- -block-flow<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>- -block-flow-md<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Using <code>:is()</code>, we're defining that direct children of the <code>body</code> and direct children when the <code>block-flow</code> class is applied will default to having our medium top margin. If including <code>body</code> is too overarching for your context, you can certainly reduce this rule to just <code>block-flow</code>.</p> <p>Here is the starter set of <code>block-flow</code> custom properties:</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--block-flow-sm</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>2rem<span class="token punctuation">,</span> 4vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--block-flow-md</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>4rem<span class="token punctuation">,</span> 8vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--block-flow-lg</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>8rem<span class="token punctuation">,</span> 16vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="gap">Gap</h3> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#gap" aria-labelledby="gap"><span hidden="">#</span></a></div> <p>Our final spacing property is <code>gap</code>, which we'll consider for layout component spacing, as in providing values for the intrinsic grid we set up earlier. We'll again be using <code>clamp()</code>, but switch it up to use <code>vmax</code> as the dynamic value for this context.</p> <p>As a reminder, <code>gap</code> is applied between elements, as the shaded area indicates.</p> <p><img src="https://moderncss.dev/img/posts/29/gap.jpeg" alt="three text elements with row and column gap space indicated by a pink shaded area" /></p> <p>And by the way - we've had <a href="https://caniuse.com/flexbox-gap">cross-browser support for <code>gap</code> in flexbox</a> since April 2021!</p> <p>Here's a baseline rule for layout <code>gap</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">gap</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6vmax<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>vmax</code> unit request the browser to use whichever is largest: the viewport width or the height.</p> <p>We're using <code>vmax</code> for the dynamic unit and not percent because percent, as applied to <code>gap</code>, is calculated based on the direction of the <code>gap</code>. So uniformly applying <code>gap</code> like this using percent may yield a smaller value for row <code>gap</code> than for column <code>gap</code>. Instead, using <code>vmax</code> creates dynamic, contextual space that will be evenly applied to both row and column <code>gap</code>.</p> <p>And - you probably guessed it - our min and max values for <code>clamp()</code> are once again potential design tokens.</p> <p>I use <code>gap</code> with grid and flex for atomics like form fields. So I wanted to explicitly name this set of custom properties as <code>layout-gap</code> since they rely on <code>vmax</code> and are intended for layout components like grids. Again, you can see the doubling strategy in effect to work out a starting <code>vmax</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--layout-gap-sm</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 3vmax<span class="token punctuation">,</span> 1.5rem<span class="token punctuation">)</span> <span class="token property">--layout-gap-md</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.5rem<span class="token punctuation">,</span> 6vmax<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--layout-gap-lg</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>3rem<span class="token punctuation">,</span> 8vmax<span class="token punctuation">,</span> 4rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="why-not-use-media-queries">Why not use media queries?</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#why-not-use-media-queries" aria-labelledby="why-not-use-media-queries"><span hidden="">#</span></a></div> <p>We've just reviewed more appropriate techniques to ensure context-dependent, intrinsic sizing and spacing of elements. Viewport-relative media queries aren't scalable for every context and are no longer the best tool for the job.</p> <p>Additionally, we'll soon have full support for container queries. Once we have access to container units, they may become the better option in some places I've still opted for viewport units in these examples.</p> <p>We are also getting the &quot;parent&quot; selector - <code>:has()</code> - which will open more possibilities for creating contextual spacing based on the actual element configuration.</p> <div class="heading-wrapper h2"> <h2 id="start-using-contextual-spacing-techniques">Start using contextual spacing techniques</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#start-using-contextual-spacing-techniques" aria-labelledby="start-using-contextual-spacing-techniques"><span hidden="">#</span></a></div> <p>Design systems and frameworks' (necessary) rigidity is at odds with intrinsic web design.</p> <p>In order to move, I'm asking each of you to try to make adjustments where you can and educate others about the possibilities. Take time to be a little more thoughtful, experiment, and push beyond your current comfort zone. Let me know how you use and improve this starter set of techniques!</p> <blockquote> <p>Check out this <a href="https://codepen.io/collection/ZMrONd/9d25332426833a3571274e21a2bde3c8">CodePen collection of a few selected methods</a>, including a layout that pulls several techniques together.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional resources</h2> <a class="anchor" href="https://moderncss.dev/contextual-spacing-for-intrinsic-web-design/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <p>This article is based on my presentation from beyond tellerrand 2022. Check out <a href="https://noti.st/st3ph/YWbRfR/scaling-css-layout-beyond-pixels">the recording and slides</a>.</p> <p>Here are some resources and other viewpoints on intrinsic web design/adaptive layout.</p> <ul> <li>Jen Simmons' talks <ul> <li><a href="https://aneventapart.com/news/post/modern-layouts-getting-out-of-our-ruts-by-jen-simmons-an-event-apart-video">Getting Out Of Our Ruts (2016)</a></li> <li><a href="https://talks.jensimmons.com/videos/h0XWcf">Everything You Know About Web Design Just Changed (2018)</a></li> <li><a href="https://www.youtube.com/watch?v=AMPKmh98XLY">Designing for Intrinsic Layouts (2020)</a></li> </ul> </li> <li>Rachel Andrew - <a href="https://aneventapart.com/news/post/making-things-better-aea-video">Making Things Better: Redefining the Technical Possibilities of CSS</a></li> <li>Ethan Marcotte's <a href="https://aneventapart.com/news/post/ethan-marcotte-a-dao-of-flexibility-video">A Dao of Flexibility</a> - the talk that introduced responsive design</li> <li>John Allsopp - <a href="https://alistapart.com/article/dao/">A Dao of Web Design</a></li> <li>Jeremy Keith <ul> <li><a href="https://adactio.com/journal/18982">Declarative design</a></li> <li><a href="https://resilientwebdesign.com/">Resilient Web Design</a></li> </ul> </li> <li>Andy Bell and Heydon Pickering - <a href="https://every-layout.dev/">Every Layout</a></li> <li>Andy Bell - <a href="https://buildexcellentwebsit.es/">BuildExcellentWebsit.es</a></li> <li>Cathy Dutton - <a href="https://alistapart.com/article/designing-for-the-unexpected/">Designing For The Unexpected</a></li> <li>Hidde de Vries - <a href="https://hidde.blog/content-based-grid-tracks-and-embracing-flexibility/">Content-based grid tracks and embracing flexibility</a></li> <li>Donnie D'Amato - <a href="https://gridless.design/">gridless.design</a></li> </ul> </content>
</entry>
<entry>
<title>Practical Uses of CSS Math Functions: calc, clamp, min, max</title>
<link href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/"/>
<updated>2021-08-15T00:00:00Z</updated>
<id>https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/</id>
<content type="html"><p>There are currently <a href="https://caniuse.com/css-math-functions">four well-supported math functions in CSS</a>. I've found each of them to be extremely useful in my daily work. These CSS functions can be used in perhaps unexpected ways, such as within gradients and color functions and in combination with CSS custom properties. We'll learn the syntax for each, view basic demos of their functionality, and explore practical use cases.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="calc"><code>calc()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#calc" aria-labelledby="calc"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>calc()</code></strong>: performing basic math operations, with the ability to interpolate between unit types (ex. <code>rem</code> to <code>vw</code>).</p> </blockquote> <p>This math function has the longest cross-browser support of the four functions we're exploring. It has a wide range of uses for any time you'd like to be able to do client-side math within your styles.</p> <p>For example, you may want something to take up most of the viewport height except the height of the navigation. For this purpose, you can mix units to pass a relative <code>vh</code> (view height) unit with an absolute pixel unit:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.content</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vh - 60px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As the viewport resizes or a user visits on larger or smaller devices, the value of <code>100vh</code> will dynamically update, and therefore so will the calculation.</p> <blockquote> <p>The benefit of <code>calc()</code> is in allowing you to avoid either hard-coding a range of magic numbers or adding a JavaScript solution to calculate the value needed to apply it as an inline style.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="using-calc-for-generative-color-palettes">Using <code>calc()</code> For Generative Color Palettes</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-calc-for-generative-color-palettes" aria-labelledby="using-calc-for-generative-color-palettes"><span hidden="">#</span></a></div> <p>We can extend the capabilities of <code>calc()</code> by passing in CSS custom properties.</p> <p>An example of this being very useful is creating a consistent color palette using <code>hsl()</code> (which stands for hue, saturation, and lightness). Given values for saturation, lightness, and a starting hue, we can calculate complementary values to build a full palette. Because of the commonality among the saturation and lightness values, the palette will feel cohesive.</p> <details open=""> <summary>CSS for "Using calc() to create an HSL color palette"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.colors</span> <span class="token punctuation">{</span> <span class="token property">--base-hue</span><span class="token punctuation">:</span> 140<span class="token punctuation">;</span> <span class="token property">--saturation</span><span class="token punctuation">:</span> 95%<span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 80%<span class="token punctuation">;</span> <span class="token property">--rotation</span><span class="token punctuation">:</span> 60<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #222<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--saturation<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--lightness<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color1</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color2</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--rotation<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color3</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-hue<span class="token punctuation">)</span> + <span class="token function">var</span><span class="token punctuation">(</span>--rotation<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .colors-974 { --base-hue: 140; --saturation: 95%; --lightness: 80%; --rotation: 60; color: #222; text-align: center; } .color-974 { padding: 0.25rem; background-color: hsl(var(--hue), var(--saturation), var(--lightness)); } .color1-974 { --hue: calc(var(--base-hue)); } .color2-974 { --hue: calc(var(--base-hue) + var(--rotation)); } .color3-974 { --hue: calc(var(--base-hue) + var(--rotation) * 2); } </style> <div class="demo no-resize"> <div class="demo--content"> <ul class="colors-974"> <li class="color-974 color1-974">Color 1</li> <li class="color-974 color2-974">Color 2</li> <li class="color-974 color3-974">Color 3</li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="clamp"><code>clamp()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#clamp" aria-labelledby="clamp"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>clamp()</code></strong>: setting boundaries on a range of acceptable values.</p> </blockquote> <p>The <code>clamp()</code> function takes three values, and order matters. The first is the lowest value in your range, the middle is your ideal value, and the third is the highest value in your range.</p> <p>An area you may have already encountered the use of <code>clamp()</code> is for fluid typography. The essential concept is that the <code>font-size</code> value can fluidly adjust based on the viewport size. This is intended to prevent large headlines triggering overflow, or taking up too much of the viewport.</p> <p>A very basic definition for a fluid <code>h1</code> style:</p> <pre class="language-css"><code class="language-css"><span class="token selector">h1</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.75rem<span class="token punctuation">,</span> 4vw + 1rem<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">read more about generating fluid type in the Modern CSS episode 12</a>.</p> <div class="heading-wrapper h3"> <h3 id="relative-responsive-padding-with-clamp">Relative Responsive Padding With <code>clamp()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#relative-responsive-padding-with-clamp" aria-labelledby="relative-responsive-padding-with-clamp"><span hidden="">#</span></a></div> <p>Another example can be seen in <a href="https://smolcss.dev/#smol-responsive-padding">my demo from SmolCSS on responsive padding</a>. The interesting thing about using percentages for padding is that it is relative to the element's width. This means it's a bit like a container-relative unit, which we can use similar to how you might think of <code>vw</code>.</p> <p>The example from SmolCSS uses the following padding definition, where the padding will grow and shrink relative to the element's width. It will never be less than <code>1rem</code>, and never greater than <code>3rem</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.element</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.5rem <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 5%<span class="token punctuation">,</span> 3rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You may have realized this again removes some scenarios where you might have previously reached for media queries. Instead of micro-managing this spacing or worrying about strictly adhering to a pixel ramp (ex 8, 12, 24, 36), you can set up sensible guidelines for a responsive transition.</p> <p>The most significant benefit here versus media queries is that since this padding definition is element relative, it will be larger when the element has more space on the page and smaller if, for example, it's placed in a narrow column. This would take a lot of coordination with media-query-based utility classes!</p> <div class="heading-wrapper h2"> <h2 id="min"><code>min()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#min" aria-labelledby="min"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>min()</code></strong>: setting boundaries on the maximum allowed value in a way that encompasses the responsive context of an element.</p> </blockquote> <p>That's right - despite being the <code>min()</code> function, the outcome is that the provided values will act as a <em>maximum</em> allowed value for the property.</p> <p>Given <code>width: min(80ch, 100vw)</code>, the outcome is that on a larger viewport, the <code>80ch</code> will be selected because it is the smaller value of the two options, yet it <em>acts like</em> a maximum based on contextually available space. Once the viewport shrinks, <code>100vw</code> will be used because it is computed as <em>smaller than</em> <code>80ch</code>, yet it's actually providing a <em>maximum</em> boundary for the element's width.</p> <div class="heading-wrapper h3"> <h3 id="the-modern-css-container-class">The Modern CSS <code>.container</code> Class</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#the-modern-css-container-class" aria-labelledby="the-modern-css-container-class"><span hidden="">#</span></a></div> <p>The example just provided is my preferred way to define a <code>.container</code>, with one tiny tweak. The <code>min()</code> function allows nested basic math operations, which means we can flip to subtracting some space as a swap for defining left and right padding, as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>80ch<span class="token punctuation">,</span> 100vw - 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On larger viewports, the element can grow to a <em>max</em> of <code>80ch</code>, and once the viewport shrinks below that width, it will be allowed to grow to <code>100vw - 2rem</code>. This definition effectively produces <code>1rem</code> of &quot;padding&quot; on either side of the element.</p> <p>In this example, you could also swap to <code>100%</code> instead of <code>vw</code> to make the element width responsive within a <em>parent container</em>, as used for this demo:</p> <details open=""> <summary>CSS for "The Modern CSS .container Class"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>40ch<span class="token punctuation">,</span> 100% - 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .container-531 { width: min(40ch, 100% - 2rem); margin-right: auto; margin-left: auto; padding: 1rem; color: #222; outline: 1px dashed; } </style> <div class="demo"> <div class="demo--content"> <div class="container-531"> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vero a quam labore inventore iste eligendi, quasi velit, qui repellendus voluptatem temporibus nisi. Pariatur nesciunt at dolorum, cumque illum maiores animi?</p> </div> </div> </div> <blockquote> <p><em>Quick note</em>: The <code>ch</code> unit is equal to the width of the <code>0</code> character given all current <code>font</code> properties at the time it is applied. This makes it an excellent choice for approximating line length for a better reading experience, for example.</p> </blockquote> <p><strong>What's the benefit</strong>? Responsive sizing <em>without</em> the need for media queries! It seems to be a common theme for these functions 😉</p> <p>The <code>min()</code> function is my most used of the math functions. Let's look at some more amazing upgrades to practical scenarios.</p> <div class="heading-wrapper h3"> <h3 id="responsive-element-sizing-with-min">Responsive Element Sizing with <code>min()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#responsive-element-sizing-with-min" aria-labelledby="responsive-element-sizing-with-min"><span hidden="">#</span></a></div> <p><strong>Any time you would like to size an element responsively, <code>min()</code> can be a great choice</strong>. For example, I explored <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/">using <code>min()</code> to control sizing an avatar</a> within a comment thread in Modern CSS episode 26.</p> <p>In the avatar example, we ended up applying <em>three</em> values with different units: <code>min(64px, 15%, 10vw)</code>. Another way to read this is that the avatar size will not exceed one of those values at any given time, with the browser selecting whichever is the <em>minimum</em> computed value.</p> <p>This definition works out to never having an avatar larger than <code>64px</code>. Particularly in a zoom scenario, the <code>10vw</code> helps the size feel more relative. And the <code>15%</code> helps keep the size relative to the element, which may have a more visually appealing result before the <code>10vw</code> applies.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="using-min-within-other-properties">Using <code>min()</code> Within Other Properties</h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-min-within-other-properties" aria-labelledby="using-min-within-other-properties"><span hidden="">#</span></a></div> <p>CSS math functions can be used in most properties that allow a numeric value. One unique place to use them is within <code>background-size</code>.</p> <p>Why? Perhaps you're supplying a layered effect of a background color and an image. And rather than using the <code>cover</code> size value, which would make the image fill the space, you would like to cap the growth of the image. This is a perfect place to bring in <code>min()</code>.</p> <p>Consider the following example, where <code>min()</code> is used to ensure the image doesn't exceed <code>600px</code> while being allowed to respond down with the element by also setting <code>100%</code>. In other words, it will grow <em>up to</em> <code>600px</code> and then resize itself down to match the element's width when it is less than <code>600px</code>.</p> <details open=""> <summary>CSS for "Controlling background-size with min()"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.background-image</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> #1F1B1C <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://source.unsplash.com/RapCPd_mJTU/800x800<span class="token punctuation">)</span></span> no-repeat center<span class="token punctuation">;</span> <span class="token property">background-size</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>600px<span class="token punctuation">,</span> 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .background-image-924 { display: grid; place-items: center; grid-template-areas: "background"; background: #1F1B1C url(https://source.unsplash.com/RapCPd_mJTU/800x800) no-repeat center; background-size: min(600px, 100%); box-shadow: inset 600px 600px rgba(0, 0, 0, 0.45); } .background-image-924 p { display: grid; place-content: center; grid-area: background; width: min(600px, 100%); outline: 1px dashed; min-height: 8rem; color: white; padding: 1rem; } </style> <div class="demo"> <div class="demo--content"> <div class="background-image-924"> <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vero a quam labore inventore iste eligendi, quasi velit.</p> </div> </div> </div> <div class="heading-wrapper h2"> <h2 id="max"><code>max()</code></h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#max" aria-labelledby="max"><span hidden="">#</span></a></div> <blockquote> <p><strong>Practical purpose of <code>max()</code></strong>: setting boundaries on the minimum allowed value in a way that encompasses the responsive context of an element.</p> </blockquote> <p>Yup, <code>max()</code> is the opposite of <code>min()</code>! So now we are setting up definitions for the <em>minimum</em> allowed values. Let's get right to the examples!</p> <div class="heading-wrapper h3"> <h3 id="contextual-margins-with-max">Contextual Margins with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#contextual-margins-with-max" aria-labelledby="contextual-margins-with-max"><span hidden="">#</span></a></div> <p>After learning about the <a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">Web Content Accessibility Guidelines (WCAG) Success Criterion 1.4.10</a> for reflow, which states that a user should be able to use zoom to magnify your site to 400%, I noticed that pixels and rems become a subpar unit in that context.</p> <p>Given a desktop size of 1280px at 400% zoom, your content is equivalent to a device at 320px. However - versus a mobile phone - the orientation is still landscape. A viewport of this size means a much-reduced area to read and perform actions. Additionally, sizes that seemed appropriate for a phone become a lot large contextually when viewed in a zoomed-in window.</p> <p>Fortunately, <code>max()</code> gives us one way to in particular handle margins more gracefully. I avoid pixel values for everything in my personal work and usually prefer <code>rem</code> for smaller spaces. But for larger spaces intended to separate content sections, I use the following, which allows relative growth for tall viewports and reduces distance for shorter viewports, which also applies to zoomed viewports.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.element + .element</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>8vh<span class="token punctuation">,</span> 2rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>On the taller viewports, <code>8vh</code> will be used, and on smaller or zoomed-in viewports, <code>2rem</code> will be used. I encourage you to try this out and spend some time testing across viewports, devices, and with and without zooming into your layout. This technique is a small upgrade that can make a significant difference for the end-user.</p> <blockquote> <p>Review an expanded example of this scenario and <a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">learn more about reflow</a> in the Modern CSS episode 27.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="prevent-browser-zoom-on-inputs-in-ios-with-max">Prevent Browser Zoom on Inputs in iOS with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#prevent-browser-zoom-on-inputs-in-ios-with-max" aria-labelledby="prevent-browser-zoom-on-inputs-in-ios-with-max"><span hidden="">#</span></a></div> <p>Have you ever experienced forced browser zoom once you focused a form input on iOS? This consequence will happen for any input that has a font-size less than <code>16px</code>.</p> <p>Here's the fix, originally linked in <a href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/">Modern CSS episode 21 about custom form input styles</a>, with full credit to <a href="https://dev.to/danburzo/css-micro-tip-make-mobile-safari-not-have-to-zoom-into-inputs-1fc1">Dan Burzo</a> for this simple solution:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>16px<span class="token punctuation">,</span> 1rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Where <code>1rem</code> could be swapped with a Sass variable or a CSS custom property. This use of <code>max()</code> ensures that regardless of another value provided, the <code>font-size</code> will be <em>at least</em> <code>16px</code> and therefore prevent the forced browser zoom.</p> <div class="heading-wrapper h3"> <h3 id="relative-focus-outlines-with-max">Relative Focus Outlines with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#relative-focus-outlines-with-max" aria-labelledby="relative-focus-outlines-with-max"><span hidden="">#</span></a></div> <p>The latest addition to my CSS reset uses <code>min()</code> to apply relative sizing for focus outlines.</p> <p>This is a reduced snippet, but by using <code>max()</code>, we ensure a <em>minimum</em> outline size of <code>2px</code>, while allowing it to <em>grow</em> relative to the element by using the font-relative <code>em</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">a</span> <span class="token punctuation">{</span> <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.08em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--outline-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">--outline-color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><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 function">var</span><span class="token punctuation">(</span>--outline-style<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--outline-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">outline-offset</span><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></code></pre> <div class="heading-wrapper h3"> <h3 id="accessible-target-sizes-with-max">Accessible Target Sizes with <code>max()</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#accessible-target-sizes-with-max" aria-labelledby="accessible-target-sizes-with-max"><span hidden="">#</span></a></div> <p>The term &quot;target size&quot; comes from <a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size-enhanced.html">WCAG Success Criterion (SC) 2.5.5</a>, where &quot;target&quot; refers to the area that will receive a pointer event (ex. mouse click or touch tap). In the upcoming WCAG 2.2, SC 2.5.5 is now the &quot;Enhanced&quot; version, which has a minimum size of <code>44px</code>.</p> <p>For this guideline, consider buttons that only use icons or the avatar from our earlier example that links to a profile. Or perhaps a dual-action button where a dropdown arrow is a separate action from the primary button control.</p> <p>In these instances, we can use <code>max()</code> similarly to when we provided a guardrail to prevent the input zooming. We'll set <code>44px</code> as one of the values within <code>max()</code> so that at <em>minimum</em>, that is the element's size.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.icon-button</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> 2em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> 2em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It should be noted that this criterion also considers the space around the element, which if combined with the element's actual size is <em>at least</em> 44px, then the criterion is passed successfully. As with all of these techniques, be sure to test with your actual product and with real users!</p> <div class="heading-wrapper h3"> <h3 id="using-max-as-a-fallback-for-css-aspect-ratio">Using <code>max()</code> As A Fallback for CSS <code>aspect-ratio</code></h3> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#using-max-as-a-fallback-for-css-aspect-ratio" aria-labelledby="using-max-as-a-fallback-for-css-aspect-ratio"><span hidden="">#</span></a></div> <p>Another way I've used <code>max()</code> is to set an image height when using <code>aspect-ratio</code> to enable an acceptable experience for browsers that do not yet support that property.</p> <p>You can see the following sample fully in use for the <a href="https://smolcss.dev/#smol-card-component">SmolCSS demo for a composable card component</a>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* Fallback for `aspect-ratio` of defining a height */</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>18vh<span class="token punctuation">,</span> 12rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* When supported, use `aspect-ratio` */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--img-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="putting-it-all-together">Putting It All Together</h2> <a class="anchor" href="https://moderncss.dev/practical-uses-of-css-math-functions-calc-clamp-min-max/#putting-it-all-together" aria-labelledby="putting-it-all-together"><span hidden="">#</span></a></div> <p>This final demo shows an example of applying multiple CSS math functions to allow responsive sizing across several properties. Note the comments alongside the demonstrated code.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="WNpXxVV" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>For more examples of using these CSS math functions and other modern CSS features, <a href="https://www.youtube.com/watch?v=dz6aFfme_hg">check out my talk from CSS Cafe on YouTube</a>.</p> </blockquote> </content>
</entry>
<entry>
<title>Modern CSS Upgrades To Improve Accessibility</title>
<link href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/"/>
<updated>2021-04-09T00:00:00Z</updated>
<id>https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/</id>
<content type="html"><p>Accessibility is a critical skill for developers doing work at any point in the stack. For front-end tasks, modern CSS provides capabilities we can leverage to make layouts more accessibly inclusive for users of all abilities across any device.</p> <p>This post will cover a range of topics:</p> <ul> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility">Focus Visibility</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-vs-source-order">Focus vs. Source Order</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow">Desktop Zoom and Reflow</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#sizing-interactive-targets">Sizing Interactive Targets</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#respecting-color-and-contrast-settings">Respecting Color and Contrast Settings</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#accessibility-learning-resources">Accessibility Learning Resources</a></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="what-does-accessible-mean">What Does &quot;Accessible&quot; Mean?</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#what-does-accessible-mean" aria-labelledby="what-does-accessible-mean"><span hidden="">#</span></a></div> <p>Accessible websites are ones that are created without barriers for users of various abilities to access content or perform actions. An internationally agreed-upon standard called the <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines</a> - or WCAG - provides success criteria to help guide you towards creating accessible experiences.</p> <p>Common accessibility barriers include:</p> <ul> <li>inability to see content or distinguish interface elements due to poor color contrast</li> <li>reduced or removed access to non-text content such as within images or charts due to failing to provide alternative text</li> <li>trapping keyboard users due to not managing focus for interactive elements</li> <li>causing headaches or worse for users with vestibular disorders due to motion and flashing/blinking animations</li> <li>preventing users of assistive technology such as screen readers from performing actions due to failure to make custom controls accommodate expected patterns</li> <li>limiting common assistive technology navigation methods due to not using semantic HTML, including heading hierarchy and landmark elements</li> </ul> <p><a href="https://www.w3.org/WAI/WCAG22/Understanding/understanding-techniques">Success criteria</a> &quot;are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc.&quot; We are going to examine a few success criteria and how modern CSS helps provide accessible solutions.</p> <div class="heading-wrapper h2"> <h2 id="focus-visibility">Focus Visibility</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-visibility" aria-labelledby="focus-visibility"><span hidden="">#</span></a></div> <p>An all too common violation that I have done myself in the past is to remove <code>:focus</code> outlines on links, buttons, and other interactive controls. Without providing an alternative <code>:focus</code> style, this is immediately a violation of the <a href="https://www.w3.org/WAI/WCAG22/Understanding/focus-appearance-minimum.html">WCAG Success Criterion 2.4.11: Focus Appearance</a>.</p> <p>Frequently, the reason this is removed is due to feeling the native browser style is not attractive or doesn't fit in with the design choices of the meta. But with modern CSS, we have a new property that can help make outlines more appealing.</p> <p>Using <code>outline-offset</code>, we can provide a positive value to position the outline away from the element. For the offset, we'll use the <code>em</code> unit to position the outline relative to the element based on its <code>font-size</code>.</p> <blockquote> <p>Bonus: We're setting the <code>outline-width</code> value using the <code>max()</code> function to ensure it doesn't shrink below a computed value of <code>1px</code> while allowing it to also be relatively sized using <code>em</code>.</p> </blockquote> <p><em>Select the demo button to display the <code>:focus</code> outline</em>.</p> <details open=""> <summary>CSS for "Focus Styles With Positive `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-359 { color: purple; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-359:focus { outline: max(1px, 0.1em) solid currentColor; outline-offset: 0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-359">Demo Button</button></p> </div> </div> <p>Alternatively, set <code>outline-offset</code> using a negative value to inset the outline from the element's perimeter.</p> <details open=""> <summary>CSS for "Focus Styles With Negative `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-260 { background-color: purple; color: white; padding: 0.5em; border-radius: 4px; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-260:focus { outline: max(1px, 0.1em) dashed currentColor; outline-offset: -0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-260">Demo Button</button></p> </div> </div> <p>There is also a new pseudo-class that you can consider using in some circumstances. The <code>:focus-visible</code> pseudo-class will display an outline (or user-defined style) only when the device/browser (user agent) determines it needs to be visible. Typically this means it will appear for keyboard users upon <code>tab</code> key interaction but not for mouse users.</p> <p>Using this update, our button styles will <em>likely</em> only show when you keyboard tab into the button.</p> <details open=""> <summary>CSS for "Focus Styles With Negative `outline-offset`"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .outline-offset-99 { background-color: blue; color: white; padding: 0.5em; border-radius: 4px; line-height: 1; font-size: 1.5rem; cursor: pointer; } .outline-offset-99:focus { outline: none; } .outline-offset-99:focus-visible { outline: max(1px, 0.1em) dashed currentColor; outline-offset: -0.25em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><button type="button" class="outline-offset-99">Demo Button</button></p> </div> </div> <p>Note that <a href="https://caniuse.com/css-focus-visible"><code>:focus-visible</code> support</a> is still rolling out to all browsers, notably missing from Safari. If you would like to try using it, here is an example of including it as a progressive enhancement.</p> <p>We're taking advantage of the fact that a browser that doesn't understand <code>:focus-visible</code> will throw away the rule that removes the outline for <code>:focus</code>. Meaning, the first rule for <code>button:focus</code> will apply to browsers that don't support <code>:focus-visible</code>, and the second two rules will apply when <code>:focus-visible</code> is supported. Interestingly, the <code>:focus:not(:focus-visible)</code> gives a false impression that <code>:focus-visible</code> is working in Safari <em>and even</em> Internet Explorer 11.</p> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus:not(:focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:focus-visible</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>1px<span class="token punctuation">,</span> 0.1em<span class="token punctuation">)</span> dashed currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</span><span class="token punctuation">:</span> -0.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="focus-vs-source-order">Focus vs. Source Order</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#focus-vs-source-order" aria-labelledby="focus-vs-source-order"><span hidden="">#</span></a></div> <p>Another focus related criterion is <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-order">Success Criterion 2.4.3: Focus Order</a>. For both visual and non-visual users, the focus order - which is typically initiated by keyboard tabbing - should proceed logically. Particularly for visual users, the focus order should follow an expected path which <em>usually</em> means following source order.</p> <p>From the criterion documentation linked previously:</p> <blockquote> <p>&quot;<em>For example, a screen reader user interacts with the programmatically determined reading order, while a sighted keyboard user interacts with the visual presentation of the Web page. Care should be taken so that the focus order makes sense to both of these sets of users and does not appear to either of them to jump around randomly.</em>&quot;</p> </blockquote> <p>Modern CSS technically provides layout properties to re-arrange visual order into something different than source order. The impact of this is potentially failing the focus order success criterion <em>if</em> there are focusable elements that are being re-arranged into a surprising order.</p> <p>If you are able, browse the following demo using your tab key and take notice of what you <em>expected</em> to happen versus what <em>actually</em> happened. When you're finished, or if a tab key is not presently an available input for you, check the box to reveal the tab order.</p> <style> [class*="source-order"] { color: #222; } .source-order-51 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; list-style: none; } .source-order-51 li { padding: 1rem; text-align: center; } .source-order-51 li a { display: grid; place-content: center; color: inherit; } .source-order__label-51 { font-weight: bold; } .source-order__label-51, .source-order__checkbox-51 { display: inline-block; margin-top: 0.5rem; margin-left: 0.5rem; margin-bottom: 1.5rem; } .source-order__checkbox-51:checked ~ ol { list-style: decimal; list-style-position: inside; } .source-order-51 li:nth-child(2) { grid-column: 3; grid-row: 2; } .source-order-51 li:nth-child(5) { grid-column: 2; grid-row: 1; } .source-order-51 li:nth-child(7) { grid-column: 1; grid-row: 1; } .source-order-51 li:nth-child(3) { grid-column: 1; grid-row: 3; } </style> <div class="demo no-resize"> <div class="demo--content"> <label class="source-order__label-51" for="source-order-51">Reveal order</label> <input class="source-order__checkbox-51" type="checkbox" id="source-order-51" /> <ol class="source-order-51"> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link</a></li> </ol> </div> </div> <p>The goal of this section is more to provide awareness of this criterion when you consider how to solve layout challenges. Upcoming in CSS grid is a native &quot;masonry&quot; solution. Unfortunately, it may negatively impact the expected focus order. Similar issues can be created when assigning specific grid areas that don't match source order, as well as customizing item order in flexbox using the <code>order</code> property.</p> <p>A recent challenge I faced was a list of navigation links that was requested to break into columns. And in this case, the logical tab order would be down one column before moving on to the next column. The catch was that it was a list of items for a single topic, so semantically it would not be ideal to break it into a list per column. Breaking into multiple lists would also misrepresent the number of items for the single topic for users of assistive tech like screen readers which announce the number of items in a list.</p> <p>Using the following set of CSS grid properties, I was able to arrive at a non-list-breaking solution:</p> <details false=""> <summary>CSS for "List Focus Order Solution With CSS Grid"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-column-gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">grid-row-gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">/* Causes items to be ordered into columns */</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token comment">/* # required to prevent a column per link */</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Size the columns */</span> <span class="token property">grid-auto-columns</span><span class="token punctuation">:</span> <span class="token function">minmax</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .grid-list-focus-order-57 { list-style: none; padding: 2rem; display: grid; grid-column-gap: 2rem; grid-row-gap: 1rem; grid-auto-flow: column; grid-template-rows: repeat(3, 1fr); grid-auto-columns: minmax(0, 33%); } .grid-list-focus-order-57 a { color: blue; } </style> <div class="demo"> <div class="demo--content"> <ul class="grid-list-focus-order-57"> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 1</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 2</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 3</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 4</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 5</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 6</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 7</a></li> <li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Link 8</a></li> </ul> </div> </div> <p>The caution here is to be mindful of the space necessary to accommodate the content. In a navigation scenario, content is usually fairly tightly controlled, so this can be a reasonable solution.</p> <blockquote> <p>Manuel Matuzovic has an excellent and more thorough <a href="https://www.matuzo.at/blog/the-dark-side-of-the-grid-part-2/">guide to considerations of CSS Grid layout and altering source order</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="desktop-zoom-and-reflow">Desktop Zoom and Reflow</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#desktop-zoom-and-reflow" aria-labelledby="desktop-zoom-and-reflow"><span hidden="">#</span></a></div> <p>You've tested across viewport sizes using browser dev tools as well as on real mobile devices, and you're happy with your site's responsive behavior. But you're probably missing one test point: desktop zoom.</p> <p>In <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410---reflow">the previous tutorial</a>, we started to look at <a href="https://www.w3.org/WAI/WCAG22/Understanding/reflow.html">WCAG Success Criterion 1.4.10 - Reflow</a>.</p> <blockquote> <p>Reflow is the term for supporting desktop zoom up to 400%. On a 1280px wide resolution at 400%, the viewport content is equivalent to 320 CSS pixels wide. The intent of a user with this setting is to trigger content to <em>reflow</em> into a single column for ease of reading.</p> </blockquote> <p>As also noted in the previous tutorial, typically zoom begins to trigger responsive behavior that you may have set using media queries. But there is currently no zoom media query. Consequently, the aspect ratio of a desktop zoomed to 400% can cause <em>reflow</em> issues with your content.</p> <p>Some examples of possible issues:</p> <ul> <li>sticky navigation that covers half <em>or more</em> of the viewport</li> <li>contained scroll areas that assume a mobile portrait aspect ratio become unscrollable/cut-off</li> <li>unwanted results when using <a href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/">fluid typography</a> techniques</li> <li>overflow or overlap issues that cut-off content</li> <li>margin and padding spacing appearing too large relative to the content size</li> </ul> <p>Without a zoom media query, it can be difficult to devise zoom solutions that are independent of device size assumptions.</p> <p>However, with modern CSS functions like <code>min()</code> and <code>max()</code>, we have tools to resolve some zoom instances without detracting from the original intent of our assumed mobile design.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>We previously looked at <a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410---reflow">using <code>min</code> to adjust a grid column</a> that contained an avatar in a way that worked for small, large, and zoomed viewports.</p> <p>Let's look at resolving vertical spacing. A common practice is for designers to create a pixel ramp for spacing, perhaps based on an 8px unit. So you would perhaps create spacing utilities that look like:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.margin-top-xs</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-sm</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-md</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 32px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-lg</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.margin-top-xl</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 128px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And so on to accommodate for the full range of your ramp. This is usually fine across a standard range of devices. But consider that the <code>xl</code> value of <code>128px</code> on a desktop zoomed to 400% becomes <em>half the viewport height</em>.</p> <p>Instead, we can update the upper end of the range to add in <code>min()</code> to select the <em>lowest computed value</em>. This means that for non-zoomed viewports, <code>128px</code> will be used. And for 400% zoomed viewports, an alternative viewport unit value may be used.</p> <p>If you are using a desktop, try out zooming to see the effect on the space between the demo elements:</p> <details open=""> <summary>CSS for "Resolving Margin Spacing For Zoom"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">section + section</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>128px<span class="token punctuation">,</span> 15vh<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .zoom-margin-spacing-487 { min-height: 150px; padding: 1rem; margin: 0 1rem; outline: 1px solid purple; outline-offset: -.5rem; color: #222; } .zoom-margin-spacing-487 + .zoom-margin-spacing-487 { margin-top: min(128px, 25vh); } </style> <div class="demo no-resize"> <div class="demo--content"> <section class="zoom-margin-spacing-487"> <h3>Section 1</h3> </section> <section class="zoom-margin-spacing-487"> <h3>Section 2</h3> </section> <section class="zoom-margin-spacing-487"> <h3>Section 3</h3> </section> </div> </div> <p>This technique can potentially be used if you encounter overlap from absolute positioned elements as well.</p> <blockquote> <p>For more examples of how zoom can effect layout and some modern CSS solutions, check out the recording of CSS Cafe meetup talk about &quot;<a href="https://www.youtube.com/watch?v=dz6aFfme_hg">Modern CSS Solutions to (Previously) Complex Problems</a>&quot;</p> </blockquote> <p>In the next section we'll see how to query for touch devices. Using that can make for an excellent combo to help determine the context of a user and present an alternate layout for things like sticky navigation or custom scroll areas.</p> <div class="heading-wrapper h2"> <h2 id="sizing-interactive-targets">Sizing Interactive Targets</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#sizing-interactive-targets" aria-labelledby="sizing-interactive-targets"><span hidden="">#</span></a></div> <p>Our next area is to consider properly sizing interactive targets, where the term &quot;target&quot; comes from <a href="https://www.w3.org/WAI/WCAG22/Understanding/target-size.html">Success Criterion 2.5.5: Target Size</a>. From that criterion:</p> <blockquote> <p>&quot;<em>The intent of this success criterion is to ensure that target sizes are large enough for users to easily activate them, even if the user is accessing content on a small handheld touch screen device, has limited dexterity, or has trouble activating small targets for other reasons. For instance, mice and similar pointing devices can be hard to use for these users, and a larger target will help them activate the target.</em>&quot;</p> </blockquote> <p>Being introduced in WCAG 2.2 is <a href="https://www.w3.org/WAI/WCAG22/Understanding/pointer-target-spacing.html">Success Criterion 2.5.8: Pointer Target Spacing</a>. Together, the guidance indicates that generally interactive controls should either:</p> <ul> <li>have a minimum actual size of 44px; <em>or</em></li> <li>allowed a minimum target size - inclusive of spacing between the control and other elements - of 44px</li> </ul> <p><em>There are exceptions and additional examples located in the linked resources to help clarify this requirement. Adrian Roselli also provides <a href="https://adrianroselli.com/2019/06/target-size-and-2-5-5.html">an excellent overview and history</a>.</em></p> <p>In addition to this site, I maintain <a href="https://smolcss.dev/">SmolCSS.dev</a> which explores minimal modern CSS solutions to create layouts and components. The following is an excerpt from one of the demonstrations - &quot;<a href="https://smolcss.dev/#smol-avatar-list">Smol Avatar List Component</a>&quot;.</p> <p>It uses the CSS function <code>max()</code> to ensure that the default display of the avatar link column is <em>at least</em> <code>44px</code> regardless of the value that may be passed within the <code>--avatar-size</code> value.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.smol-avatar-list</span> <span class="token punctuation">{</span> <span class="token property">--avatar-size</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">--avatar-count</span><span class="token punctuation">:</span> 3<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token comment">/* Default to displaying most of the avatar to enable easier access on touch devices `max` ensures the WCAG touch target size is met or exceeded */</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--avatar-count<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">max</span><span class="token punctuation">(</span>44px<span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--avatar-size<span class="token punctuation">)</span> / 1.15<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, when a hover-capable device that also is likely to take a &quot;fine&quot; input such as a mouse or stylus is detected, the solution allows the avatars to overlap. This allowance is due to an animation on <code>:hover</code> and <code>:focus</code> - two interactions not available for touch-only devices - that reveals the avatar fully and meets the target size in those states.</p> <p>This snippet shows the media query that allows that detection:</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">any-hover</span><span class="token punctuation">:</span> hover<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">any-pointer</span><span class="token punctuation">:</span> fine<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Allow avatars to overlap by shrinking grid cell width */</span> <span class="token punctuation">}</span></code></pre> <p>Using device capability detection, we can offer experiences that allow meeting this criterion while also still allowing design flexibility.</p> <blockquote> <p><strong>Always test solutions</strong> with real devices based on data about your real users.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="reducing-motion">Reducing Motion</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#reducing-motion" aria-labelledby="reducing-motion"><span hidden="">#</span></a></div> <p>Some of your users may have vestibular disorders and are at risk of headaches or even seizures due to motion and flashing/blinking animations.</p> <p>There are three main criteria:</p> <ul> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes-or-below-threshold.html">Success Criterion 2.3.1 Three Flashes or Below Threshold</a> - Web pages do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds.</li> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/three-flashes">Success Criterion 2.3.2: Three Flashes</a> - Web pages do not contain anything that flashes more than three times in any one second period</li> <li><a href="https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions">Success Criterion 2.3.3: Animation from Interactions</a> - Motion animation triggered by interaction can be disabled, unless the animation is essential to the functionality or the information being conveyed.</li> </ul> <blockquote> <p>For more comprehensive information, Val Head is a leading expert and has written extensively about this criteria, including this <a href="https://css-tricks.com/accessible-web-animation-the-wcag-on-animation-explained/">CSS-Tricks article covering the motion-related WCAG criteria</a>.</p> </blockquote> <p>Modern CSS gives us a media feature query that we can use to test for a users OS-set preference about motion. While your base animations/transitions should meet the flash related thresholds as noted in WCAG criteria, you should prevent them entirely if a user prefers reduced motion.</p> <p>Here's a quick rule set to globally handle for this, courtesy of Andy Bell's <a href="https://piccalil.li/blog/a-modern-css-reset">Modern CSS Reset</a>.</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Remove all animations and transitions for people that prefer not to see them */</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">*, *::before, *::after</span> <span class="token punctuation">{</span> <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">scroll-behavior</span><span class="token punctuation">:</span> auto <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Animations are ran once, and any transitions will happen instantly. In some instances, you'll want to plan animations for this possibility to ensure they &quot;freeze&quot; on the desirable frame.</p> <p>This demo includes a gently pulsing circle unless a user has selected reduced motion.</p> <details false=""> <summary>CSS for "Demo of `prefers-reduced-motion`"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> @keyframes pulse { 0% { transform: scale(0.85); } 100% { transform: scale(1.25); } } .prefers-reduced-motion-807 { width: 3rem; height: 3rem; border-radius: 50%; border: 3px solid purple; animation: pulse 2000ms infinite alternate ease-in-out; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <div class="prefers-reduced-motion-807"></div> </div> </div> <p>You can test results of this media feature query within Chrome/Edge by opening dev tools and selecting the kebab menu (3 vertical dots), then &quot;More tools&quot; and &quot;Rendering&quot;. Then you can toggle settings in the section for &quot;Emulate CSS media feature prefers-reduced motion&quot;.</p> <img src="https://moderncss.dev/img/posts/27/chrome-prefers-reduced-motion.png" alt="preview of the dev tools panel for this setting as described in the previous text" width="400" /> <div class="heading-wrapper h2"> <h2 id="respecting-color-and-contrast-settings">Respecting Color and Contrast Settings</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#respecting-color-and-contrast-settings" aria-labelledby="respecting-color-and-contrast-settings"><span hidden="">#</span></a></div> <p>Dark mode seems to be a fad, but for some users it's essential for ensuring they can read your content. While there are currently no guidelines instructing that both a dark and light mode for content is required, you can begin to be future-friendly by considering offering both modes.</p> <p>What <em>is</em> a requirement is that if you do offer dark mode that is continues to pass at least the standard color contrast guidelines available in <a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum">Success Criterion 1.4.3: Contrast (Minimum)</a>.</p> <p>The minimum contrast ratios to meet are at least:</p> <ul> <li>4.5:1 for normal text</li> <li>3:1 for large text - defined as 18.66px <strong>and</strong> bold or larger, or 24px and larger</li> <li>3:1 for graphics and user interface components (such as form input borders)</li> </ul> <blockquote> <p><strong>Head's up</strong>: A new color contrast model is being considered for WCAG 3, but it will be a few years before it becomes the standard. You can learn more about the proposed Advanced Perceptual Contrast Algorithm (APCA) in the <a href="https://w3c.github.io/silver/guidelines/methods/Method-font-characteristic-contrast.html">WCAG 3 draft guidelines</a> (these may change over time until it is standard).</p> </blockquote> <p>Whether or not you provide dark and light mode, users of Windows 10 devices can select a system setting to enable <a href="https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696">High Contrast Mode</a>.</p> <p>In most cases, you should allow the user's settings in this mode to be applied. Occasionally, the application of the high contrast settings means colors that are important to understanding your interface will be removed.</p> <p>Three often critical properties that will usually be removed in Windows High Contrast Mode:</p> <ul> <li><code>box-shadow</code></li> <li><code>background-color</code></li> <li><code>background-image</code> unless it contains a <code>url()</code> value</li> </ul> <p>And two critical properties that will have their color swapped for the &quot;system color&quot; equivalent:</p> <ul> <li><code>color</code></li> <li><code>border-color</code></li> </ul> <blockquote> <p>You can <a href="https://www.w3.org/TR/css-color-adjust-1/#forced-colors-properties">review a full list of properties</a> affected by this &quot;forced color&quot; mode.</p> </blockquote> <p>Sometimes these properties can be compensated by using transparent alternates. For example, if you are using <code>box-shadow</code> as an alternate to <code>outline</code> in order to match <code>border-radius</code> on <code>:focus</code>, you should still include an <code>outline</code> with the color value set to <code>transparent</code> as the complement to retain a <code>:focus</code> style for forced color modes. This example is included in my <a href="https://moderncss.dev/css-button-styling-guide/">CSS button styling guide</a>. In a similar way, you can include a transparent border in place of <code>box-shadow</code> if the shadow was intended to communicate an important boundary.</p> <p>If you're using SVG icons, passing <code>currentColor</code> as the value of <code>fill</code> or <code>stroke</code> will help ensure they respond to the forced color settings.</p> <p>Or, you can use a special media feature query to assign colors from the <a href="https://www.w3.org/TR/css-color-4/#typedef-system-color">system color palette</a>. These respect user settings while allowing you to ensure your critical interface elements retain color.</p> <p>Here's an example of using simple CSS shapes and gradients as icons, and ensuring they retain color within a forced color mode. Within the <code>forced-colors</code> feature query, we only need to set the <code>color</code> property because we've set up the icons to use the <code>currentColor</code> value.</p> <details false=""> <summary>CSS for "Demo of forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.icon</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.filled</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gradient</span> <span class="token punctuation">{</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">repeating-linear-gradient</span><span class="token punctuation">(</span> 45deg<span class="token punctuation">,</span> currentColor<span class="token punctuation">,</span> currentColor 2px<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 2px<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 6px <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">forced-colors</span><span class="token punctuation">:</span> active<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.icon</span> <span class="token punctuation">{</span> // Required to enable colors <span class="token property">forced-color-adjust</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> // User-preferred &amp;quot<span class="token punctuation">;</span>text&amp;quot<span class="token punctuation">;</span> color <span class="token property">color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .icon-489 { width: 1.5rem; height: 1.5rem; display: inline-block; color: blue; } .filled-489 { background-color: currentColor; border-radius: 50%; } .gradient-489 { background-image: repeating-linear-gradient( 45deg, currentColor, currentColor 2px, rgba(255, 255, 255, 0) 2px, rgba(255, 255, 255, 0) 6px ); border: 1px solid; } @media screen and (forced-colors: active) { .icon-489 { // Required to enable colors forced-color-adjust: none; // User-preferred "text" color color: CanvasText; } } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <p><span class="icon-489 filled-489"></span> <span class="icon-489 gradient-489"></span></p> </div> </div> <p>Using Windows High Contrast Mode, a likely outcome based on defaults would be that the blue becomes white due to the <code>CanvasText</code> color keyword while the page background becomes black.</p> <blockquote> <p>Review more about how this mode works with a more extensive example on the <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">Microsoft Edge Blog</a></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="accessibility-learning-resources">Accessibility Learning Resources</h2> <a class="anchor" href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/#accessibility-learning-resources" aria-labelledby="accessibility-learning-resources"><span hidden="">#</span></a></div> <p>While we linked to success criteria and other resources throughout the examples, there is a lot more to learn!</p> <p>Here are some additional resources to learn more:</p> <ul> <li><a href="https://a11y.coffee/">a11y.coffee</a></li> <li><a href="https://solidstart.info/">solidstart.info</a></li> <li>This extensive guide about <a href="https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/">accessible front-end components from Smashing Magazine</a></li> <li>Search and explore the <a href="https://www.getstark.co/library">library of resources available from Stark</a></li> </ul> <p>I've created a variety of resources as well:</p> <ul> <li>An <a href="https://dev.to/5t3ph/introduction-to-web-accessibility-5cmp">intro to accessibility</a> from my beginner's webdev series</li> <li>Practice resolving issues with my <a href="https://github.com/5t3ph/a11y-fails">a11y fails project</a></li> <li>Listen to the two part Word Wrap podcast series (that I co-host) on <a href="https://wordwrap.dev/episodes/008/">common accessibility failures</a> and <a href="https://wordwrap.dev/episodes/009/">WCAG success criteria you may not be meeting</a></li> <li>Learn about handling stateful button contrast and generate an accessible palette with my web app <a href="https://buttonbuddy.dev/">ButtonBuddy</a></li> <li>Use the <a href="https://www.npmjs.com/package/a11y-color-tokens">a11y-color-tokens package</a> to speed up generating an accessible color palette</li> </ul> <blockquote> <p>I enjoy talking about accessibility and do my best to design accessible tutorials and callout any accessibility specifics. If you spot an error or want to suggest an improvement, <a href="https://thinkdobecreate.com/contact/">contact me</a>.</p> </blockquote> </content>
</entry>
<entry>
<title>Developing For Imperfect: Future Proofing CSS Styles</title>
<link href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/"/>
<updated>2021-03-28T00:00:00Z</updated>
<id>https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/</id>
<content type="html"><p>How do we plan future-proof styles in a world with an infinite degree of device and user ability variance? Let's explore how things can break and how modern CSS provides solutions.</p> <p>This episode will cover handling for:</p> <ul> <li>variable content length and overflow</li> <li>unpredictable media sizes</li> <li>internationalization</li> <li>accessibility-related user settings</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>We'll explore creating a robust comment thread component. Here's our starting point - an exact mimic of the layout you received from design, good job!</p> <details> <summary>CSS for "Initial Comment Thread"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment-list</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment .comment-list</span> <span class="token punctuation">{</span> <span class="token property">grid-column-start</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span> <span class="token property">grid-column-end</span><span class="token punctuation">:</span> -1<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 64px 1fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-body</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> .5rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #444<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-meta</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #767676<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> .875rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-body a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-meta a</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> mediumvioletred<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-883 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-883 .comment-list-883 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-883 { display: grid; grid-template-columns: 64px 1fr; gap: 1rem; } .comment-body-883 { display: grid; gap: .5rem; color: #444; } .comment-meta-883 { color: #767676; font-size: .875rem; } .comment-body-883 a { color: inherit; } .comment-meta-883 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-883"> <li class="comment-883"> <span class="comment-avatar"><img alt="@baywriter avatar" src="https://moderncss.dev/img/posts/26/avatar1.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter</a> <em>15 mins ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> <li class="comment-883"> <span class="comment-avatar"><img alt="@michelle avatar" src="https://moderncss.dev/img/posts/26/avatar2.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle</a> <em>1 hour ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> <ul class="comment-list-883"> <li class="comment-883"> <span class="comment-avatar"><img alt="@claudia87 avatar" src="https://moderncss.dev/img/posts/26/avatar3.png" /></span> <div class="comment-body-883"> <small class="comment-meta-883"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>But if you resize it, you'll already notice a few problems, particularly with overflow.</p> <p><small><em>Avatar illustrations are part of the <a href="https://blush.design/collections/women-power">Women Power</a> collection on Blush by Sara Pelaez</em>.</small></p> <div class="heading-wrapper h2"> <h2 id="responsive-planning">Responsive Planning</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#responsive-planning" aria-labelledby="responsive-planning"><span hidden="">#</span></a></div> <p>When you can't precisely plan around content, plan for flexibility. Rather than setting absolutes, we can use CSS functions to choose the best value relative to the current context.</p> <p>In our <code>.comment</code> styles, we set a precise pixel value for the avatar. Instead, we can use the CSS function <code>min</code> to select the <em>minimum computed value</em> between a list of options.</p> <details open=""> <summary>CSS for "Updated Avatar Grid Column"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.comment</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 64px 1fr<span class="token punctuation">;</span></del> <ins class="highlight-line highlight-line-add"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></ins> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .comment-list-403 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-403 .comment-list-403 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-403 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-403 { display: grid; gap: .5rem; color: #444; } .comment-meta-403 { color: #767676; font-size: .875rem; } .comment-body-403 a { color: inherit; } .comment-meta-403 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-403"> <li class="comment-403"> <span class="comment-avatar"><img alt="@baywriter avatar" src="https://moderncss.dev/img/posts/26/avatar1.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter</a> <em>15 mins ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> <li class="comment-403"> <span class="comment-avatar"><img alt="@michelle avatar" src="https://moderncss.dev/img/posts/26/avatar2.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle</a> <em>1 hour ago</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> <ul class="comment-list-403"> <li class="comment-403"> <span class="comment-avatar"><img alt="@claudia87 avatar" src="https://moderncss.dev/img/posts/26/avatar3.png" /></span> <div class="comment-body-403"> <small class="comment-meta-403"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Lemon drops danish soufflé gummies dragée apple pie. Pudding gummi bears gingerbread cotton candy toffee. Caramels halvah sweet roll lollipop chocolate.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>In this case, the impact is that the avatar will never exceed <code>64px</code> for larger viewports. And for smaller viewports <em>or</em> within narrower containers, it will be computed as <code>15%</code> of the total comment width.</p> <p>As this example shows, sometimes we can turn over layout choices to the browser to make contextually versus define precise values within media queries.</p> <div class="heading-wrapper h2"> <h2 id="always-expect-more">Always Expect More</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#always-expect-more" aria-labelledby="always-expect-more"><span hidden="">#</span></a></div> <p>Whether characters or elements, always expect more than the original design has planned.</p> <p>Our avatar update has already improved things. But we're still viewing the component with &quot;happy path&quot; content from our designer that doesn't reflect real-world data. Notably, the user names and comment lengths are relatively short.</p> <p>Let's update our demo data to have a bit more variance and &quot;real&quot; avatars:</p> <style> .comment-list-754 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-754 .comment-list-754 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-754 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-754 { display: grid; gap: .5rem; color: #444; } .comment-meta-754 { color: #767676; font-size: .875rem; } .comment-body-754 a { color: inherit; } .comment-meta-754 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-754"> <li class="comment-754"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-754"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-754"> <li class="comment-754"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-754"> <small class="comment-meta-754"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Not as pretty as our mockup mimic 😊</p> <p>Now, if you've been following along and testing our component via resizing, you'll see we have the possibility of content overflow.</p> <p>Let's resolve it first for the <code>.comment-meta</code>, which is the <code>small</code> tag containing the username and date.</p> <p>We will update the layout method to allow the username and date to line up on wider viewports and stack on smaller viewports. Simple flex behavior allows this since child elements will be their max-width when there's room and flow to a new row when the containing element reduces below that max-width.</p> <details open=""> <summary>CSS for "Update Comment Meta Layout"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment-meta</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-209 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-209 .comment-list-209 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-209 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; } .comment-body-209 { display: grid; gap: .5rem; color: #444; } .comment-meta-209 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-209 a { color: inherit; } .comment-meta-209 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-209"> <li class="comment-209"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-209"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-209"> <li class="comment-209"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-209"> <small class="comment-meta-209"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>While <a href="https://caniuse.com/flexbox-gap">flexbox <code>gap</code> support</a> is gaining, in this case, the degraded behavior is simply very slightly closer elements, so it isn't too detrimental not to provide a fallback.</p> <p>Go ahead and test this version and see how the dates bump to a new line when there isn't enough space for their full width.</p> <div class="heading-wrapper h3"> <h3 id="preventing-content-overflow">Preventing Content Overflow</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#preventing-content-overflow" aria-labelledby="preventing-content-overflow"><span hidden="">#</span></a></div> <p>In the demo data, the longer email in the second comment eventually causes overflow scroll on smaller viewports. So does the extended URL in the comment body.</p> <p>The fix could be scoped to only the link elements if we'd like. However, due to the nature of a comment thread, it seems to make sense to be extra-preventative about overflow content in this context So we'll apply two properties to the top-level <code>.comment</code> in order to cascade to all the content within.</p> <details open=""> <summary>CSS for "Preventing Content Overflow"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token comment">/* Help prevent overflow of long words/names/URLs */</span> <span class="token property">overflow-wrap</span><span class="token punctuation">:</span> break-word<span class="token punctuation">;</span> <span class="token comment">/* Optional, not supported for all languages */</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment a</span> <span class="token punctuation">{</span> <span class="token comment">/* Remove from links to prevent perceiving false hyphens */</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-378 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-378 .comment-list-378 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-378 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-378 { display: grid; gap: .5rem; color: #444; } .comment-meta-378 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-378 a { color: inherit; hyphens: none; } .comment-meta-378 a { color: mediumvioletred; } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-378"> <li class="comment-378"> <span class="comment-avatar"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-378"> <span class="comment-avatar"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-378"> <li class="comment-378"> <span class="comment-avatar"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-378"> <small class="comment-meta-378"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Notice we removed the possibility of <code>hyphens</code> from links. In this case, the full links are visible like in our example, and someone tries to write it down or read it aloud.</p> <blockquote> <p>CSS-inserted hyphens are not included if a user copies the text. As noted, <code>hyphens</code> are also not consistently available for all languages in all browsers. You can <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#browser_compatibility">review <code>hyphens</code> support on MDN</a>.</p> </blockquote> <p>With <code>overflow-wrap: break-word</code>, any text string <em>may</em> be broken onto a new line once the containing element doesn't have room for its full-width. When <code>hyphens</code> are supported, the bonus effect is reducing a &quot;ragged edge&quot; from odd spaces caused by broken words.</p> <p>Optionally, you may want to update links to use <code>overflow-wrap: anywhere;</code> to prevent an empty space if the browser decides to move the link to a new line before applying the break. You can see our current solution on smaller viewports currently leaves a space before the long exposed link.</p> <p>Try out resizing the component now, and perhaps even pop open dev tools to inspect and toggle on and off these properties to see the difference in their effects.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="handling-variable-media-dimensions">Handling Variable Media Dimensions</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#handling-variable-media-dimensions" aria-labelledby="handling-variable-media-dimensions"><span hidden="">#</span></a></div> <p>Now let's deal with those avatars.</p> <p>First, we set <code>border-radius</code> to create a circle appearance. Then we ensure the image fills the grid column with <code>width: 100%</code>. Following that, we turn the image into its own container and allow the image content to fill but not exceed the <code>img</code> dimensions with <code>object-fit: cover</code>. We end the rule with a <a href="https://caniuse.com/mdn-css_properties_aspect-ratio">cutting-edge property of <code>aspect-ratio</code></a> to ensure a perfect square.</p> <details open=""> <summary>CSS for "Updated Avatar Dimensions"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment img</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.comment-avatar</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.comment-avatar img</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-890 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-890 .comment-list-890 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-890 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-890 { display: grid; gap: .5rem; color: #444; } .comment-meta-890 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-890 a { color: inherit; hyphens: none; } .comment-meta-890 a { color: mediumvioletred; } .comment-avatar-890 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-890 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-890 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-890"> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-890"> <li class="comment-890"> <span class="comment-avatar-890"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-890"> <small class="comment-meta-890"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>We follow that rule with a feature detection fallback rule - <code>@supports not (aspect-ratio: 1)</code> - for browsers that do <em>not</em> support <code>aspect-ratio</code>. This fallback is an older technique that relies on padding to ensure a perfect square ratio of the image's parent and then ensures the <code>img</code> fills that area.</p> <blockquote> <p>Previous Modern CSS tutorials have covered <code>object-fit</code>, such as <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">CSS-Only Full-Width Responsive Images 2 Ways</a>. You may also enjoy this <a href="https://egghead.io/lessons/css-apply-aspect-ratio-sizing-to-images-with-css-object-fit">3 min video demonstrating <code>object-fit</code></a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="breaking-behavior-to-test">Breaking Behavior To Test</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#breaking-behavior-to-test" aria-labelledby="breaking-behavior-to-test"><span hidden="">#</span></a></div> <p>We've covered the scenarios we could detect fairly easily by resizing our browser/the component container. And adding more real-world data helped us define better avatar styles.</p> <p>There are a few more items we need to explicitly test for: internationalization (i18n) and a few relevant WCAG success criteria for accessibility.</p> <blockquote> <p><strong>Terminology check</strong>: WCAG stands for the &quot;<a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines</a>,&quot; a set of standards intended to help create more accessible and inclusive experiences. <em><a href="https://www.w3.org/WAI/WCAG22/Understanding/understanding-techniques">Success Criteria</a></em> are guidance that is broadly applicable to current and future web technologies in order to assist in creating experiences that are accessible.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="internationalization-i18n">Internationalization (i18n)</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#internationalization-i18n" aria-labelledby="internationalization-i18n"><span hidden="">#</span></a></div> <p>Yes, the comments are silly nonsense (courtesy of <a href="http://www.cupcakeipsum.com/">cupcake ipsum</a> and <a href="https://www.cipsum.com/">corporate ipsum</a>). However, for something like a comment thread component that's purpose is to intake and display user-submitted content, it's a great idea to stress-test by trialing some translations.</p> <p>The first comment is German, the second is Estonian, and the third is Arabic.</p> <details open=""> <summary>CSS for "RTL Text Styling"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.comment</span> <span class="token punctuation">{</span> <span class="token property">text-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .comment-list-732 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-732 .comment-list-732 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-732 { display: grid; grid-template-columns: min(64px, 15%) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; text-align: start; } .comment-body-732 { display: grid; gap: .5rem; color: #444; } .comment-meta-732 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-732 a { color: inherit; hyphens: none; } .comment-meta-732 a { color: mediumvioletred; } .comment-avatar-732 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-732 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-732 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-732"> <li lang="de" class="comment-732"> <span class="comment-avatar-732"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>Vor 15 Minuten</em></small> <p>Podcasting des operativen Änderungsmanagements innerhalb von Workflows, um ein Framework zu erstellen. Schalten Sie nahtlose Leistungsindikatoren offline, um den Long Tail zu maximieren.</p> </div> </li> <li lang="et" class="comment-732"> <span class="comment-avatar-732"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 tund tagasi</em></small> <p>Kasutage agaraid raamistikke, et pakkuda põhjalikku ülevaadet kõrgetasemeliste ülevaadete jaoks. Iteratiivsed lähenemised ettevõtte strateegiale soodustavad koostöömõtlemist, et edendada üldist väärtuspakkumist.</p> <p>Kasvata orgaaniliselt terviklikku maailmavaadet häirivast innovatsioonist töökoha mitmekesisuse ja mõjuvõimu suurendamise kaudu.</p> </div> <ul class="comment-list-732"> <li lang="ar" dir="rtl" class="comment-732"> <span class="comment-avatar-732"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-732"> <small class="comment-meta-732"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>31 أكتوبر 2021 الساعة 6:54 مساءً</em></small> <p>سيؤدي الانغماس في تقنية النانو على طول طريق المعلومات السريع إلى إغلاق الحلقة حول التركيز فقط على المحصلة النهائية</p> </div> </li> </ul> </li> </ul> </div> </div> <p>Thanks to our previous work on handling overflow, our comment thread is gracefully handling the change in content languages.</p> <p>On the third one that is in Arabic, the browser is handling the content direction switch firstly due to placing the attribute <code>dir=&quot;rtl&quot;</code> on the <code>.comment</code> list element. Interestingly, the browser intelligently switches the order of the <code>grid-template-columns</code> without our needing to do anything extra. Flexbox will also flip according to this attribute. Older styles that use floats would not flip and would require an additional override.</p> <p>We've defined just one extra property: <code>text-align: start</code>. This is called a <em><a href="https://rtlstyling.com/posts/rtl-styling#css-logical-properties">logical property</a></em> and in the case of RTL being defined it flips the text and becomes equivalent to <code>text-align: right</code>. While <a href="https://caniuse.com/css-logical-props">support is still gaining for logical properties</a>, you may need to include a fallback. Since we're using <code>gap</code> for spacing throughout, no update is needed there. If we were using margins that were affected, we could again use logical properties to help do the conversion when needed.</p> <p>Since I am not an RTL (right-to-left) styling expert, I will point you to this fantastic resource if you would like to <a href="https://rtlstyling.com/posts/rtl-styling/">learn more about RTL styling</a>.</p> <div class="heading-wrapper h3"> <h3 id="wcag-success-criterion-1410-reflow">WCAG Success Criterion 1.4.10 - Reflow</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1410-reflow" aria-labelledby="wcag-success-criterion-1410-reflow"><span hidden="">#</span></a></div> <p>Reflow is the term for supporting desktop zoom up to 400%. On a 1280px wide resolution at 400%, the viewport content is equivalent to 320 CSS pixels wide.</p> <p>Zooming on a desktop eventually triggers what we usually think of as &quot;responsive design&quot; behavior. In fact, if you are using media queries or other viewport-based layout methods, you will see those begin to take hold as you zoom in.</p> <p>The trouble with handling this success criterion is usually two-fold:</p> <ul> <li>there is no <code>zoom</code> media query to adjust for any issues</li> <li>the aspect ratio of a desktop using zoom is different than the mobile portrait mode we usually plan responsive design around</li> </ul> <p>The aspect ratio difference, in particular, can cause issues with overlap. It also means solutions that rely on only viewport units or percentages appear either too large or too small within a zoom context.</p> <p>However, viewport units used in combination with other units can actually help solve zoomed layout issues as well and gap-fill the problem of not having a dedicated <code>zoom</code> media query.</p> <p>If we zoom our component to 400%, the avatar column begins to grow a bit large within that context. We'd like it to take up a relatively similar size column as we perceive it at standard zoom.</p> <p>Recall that we originally applied <code>min</code> to the avatar's grid column, which was intended to resize the avatar for smaller containers and viewports via a percentage width. Fortunately, the <code>min</code> function can take more than two values!</p> <p>Now, this type of fix can take some trial and error, but to my eye, adding <code>10vw</code> as an additional value provided the desired adjustment. It also slightly reduced the avatar for true mobile viewports but was a worthwhile trade-off.</p> <p>The benefit of retaining the percentage width is that our component continues to be responsive to its parent container as well. If we removed it, we would not see a reduction until the viewport units began to take effect, which may objectively be too late.</p> <details open=""> <summary>CSS for "Update Column Minimum Allowed Widths"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.comment</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></del> <ins class="highlight-line highlight-line-add"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>64px<span class="token punctuation">,</span> 15%<span class="token punctuation">,</span> 10vw<span class="token punctuation">)</span> 1fr<span class="token punctuation">;</span></ins> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .comment-list-381 { list-style: none; padding: 0.5rem; margin: 0; display: grid; gap: 1.5rem; } .comment-381 .comment-list-381 { grid-column-start: 2; grid-column-end: -1; padding: 0; } .comment-381 { display: grid; grid-template-columns: min(64px, 15%, 10vw) 1fr; gap: 1rem; overflow-wrap: break-word; hyphens: auto; } .comment-body-381 { display: grid; gap: .5rem; color: #444; } .comment-meta-381 { color: #767676; font-size: .875rem; display: flex; flex-wrap: wrap; gap: 0.5rem; } .comment-body-381 a { color: inherit; hyphens: none; } .comment-meta-381 a { color: mediumvioletred; } .comment-avatar-381 img { border-radius: 50%; width: 100%; object-fit: cover; aspect-ratio: 1; } @supports not (aspect-ratio: 1) { .comment-avatar-381 { position: relative; height: 0; padding-bottom: 100%; } .comment-avatar-381 img { position: absolute; height: 100%; } } </style> <div class="demo"> <div class="demo--content"> <ul class="comment-list-381"> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@baywriter_cactusmom avatar" src="https://moderncss.dev/img/posts/26/avatarr1.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@baywriter_cactusmom</a> <em>15 mins ago</em></small> <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p> </div> </li> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@michelle_n_catz@superremail.co.uk avatar" src="https://moderncss.dev/img/posts/26/avatarr2.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small> <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p> <p>Have you seen <a href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p> </div> <ul class="comment-list-381"> <li class="comment-381"> <span class="comment-avatar-381"><img alt="@claudia87_author avatar" src="https://moderncss.dev/img/posts/26/avatarr3.jpeg" /></span> <div class="comment-body-381"> <small class="comment-meta-381"><a href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small> <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p> <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p> </div> </li> </ul> </li> </ul> </div> </div> <p>If you are using a desktop browser, bump up your zoom to 400%. Then open dev tools (recommended not to place it as a sidebar for this test) and find this demo. Select one of the <code>.comment</code> list items and adjust the <code>10vw</code> by using your arrow keys to see when it &quot;wins&quot; versus the other values. Then, swap to a mobile emulator and view whether your adjustment positively or negatively impacted the avatar in that view.</p> <p>Resolving zoom layout issues takes some patience, but <code>min</code> is one of the best modern CSS tools I've found to assist with this challenge.</p> <div class="heading-wrapper h3"> <h3 id="wcag-success-criterion-1412-text-spacing">WCAG Success Criterion 1.4.12 - Text Spacing</h3> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#wcag-success-criterion-1412-text-spacing" aria-labelledby="wcag-success-criterion-1412-text-spacing"><span hidden="">#</span></a></div> <p>Another criterion you may not be aware of or already be testing for is <em>Text Spacing</em>.</p> <p>According to the <a href="https://www.w3.org/WAI/WCAG22/Understanding/text-spacing.html">text spacing understanding documentation</a>, your content should be flexible for user settings, including:</p> <ul> <li>Line height (line spacing) to at least 1.5 times the font size</li> <li>Spacing following paragraphs to at least 2 times the font size</li> <li>Letter spacing (tracking) to at least 0.12 times the font size</li> <li>Word spacing to at least 0.16 times the font size</li> </ul> <p>Luckily, there is a <a href="https://dylanb.github.io/bookmarklets.html">text spacing bookmarklet</a> you can grab that will apply styles to test this criterion.</p> <p>If you're unfamiliar with a bookmarklet, you can click the link and drag it to your bookmark bar. In this case, the bookmarklet contains a tiny script that will apply the text spacing styles to all elements on the page you're viewing to allow you to test this criterion.</p> <p>Applying the bookmarklet test to our comment thread component, we, fortunately, encounter no issues thanks to our previous efforts.</p> <blockquote> <p>You may have difficulties with this criterion if you try to define content boxes with absolute dimensions, or rely on CSS truncation methods, or force inline dimension styles with JavaScript. If the content is cut-off or overflows, you need to resolve it to meet this criterion.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="final-demo">Final Demo</h2> <a class="anchor" href="https://moderncss.dev/developing-for-imperfect-future-proofing-css-styles/#final-demo" aria-labelledby="final-demo"><span hidden="">#</span></a></div> <form action="https://codepen.io/pen/define" method="POST" target="_blank"> <input type="hidden" name="data" value='{"title":"Modern CSS Solutions - Comment Thread Component","description":"Generated from: ModernCSS.dev/developing-for-imperfect-future-proofing-css-styles/","tags":["moderncss"],"editors":"110","layout":"left","html":"<!-- Modern CSS Solutions - Comment Thread Component\nGenerated from: ModernCSS.dev/developing-for-imperfect-future-proofing-css-styles/ -->\n<ul class=\"comment-list\">\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@baywriter_cactusmom avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr1.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@baywriter_cactusmom</a> <em>15 mins ago</em></small>\n <p>Podcasting operational change management inside of workflows to establish a framework. Taking seamless key performance indicators offline to maximise the long tail.</p>\n </div>\n </li>\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@michelle_n_catz@superremail.co.uk avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr2.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@michelle_n_catz@superremail.co.uk</a> <em>1 hour ago</em></small>\n <p>Leverage agile frameworks to provide a robust synopsis for high level overviews. Iterative approaches to corporate strategy foster collaborative thinking to further the overall value proposition. Organically grow the holistic world view of disruptive innovation via workplace diversity and empowerment.</p>\n <p>Have you seen <a href=\"https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/\">https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</a>? Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution.</p>\n </div>\n <ul class=\"comment-list\">\n <li class=\"comment\">\n <span class=\"comment-avatar\"><img alt=\"@claudia87_author avatar\" src=\"https://moderncss.dev/img/posts/26/avatarr3.jpeg\" /></span>\n <div class=\"comment-body\">\n <small class=\"comment-meta\"><a href=\"#\">@claudia87_author</a> <em>October 31, 2021 at 6:54 PM</em></small>\n <p>Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps.</p>\n <p>Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.</p>\n </div>\n </li>\n </ul>\n </li>\n</ul>\n","html_pre_processor":"none","css":"/* Box sizing rules */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* Remove default margin */\nbody,\nh1,\nh2,\nh3,\nh4,\np {\n margin: 0;\n}\n\n/* Set core body defaults */\nbody {\n min-height: 100vh;\n text-rendering: optimizeSpeed;\n line-height: 1.5;\n font-family: system-ui, sans-serif;\n}\n\n/* Make images easier to work with */\nimg {\n display: block;\n max-width: 100%;\n}\n\n/***\n 🟣 Modern CSS Solutions Demo Styles\n */\n\n.comment-list {\n list-style: none;\n padding: 0.5rem;\n margin: 0;\n display: grid;\n gap: 1.5rem;\n}\n\n.comment .comment-list {\n grid-column-start: 2;\n grid-column-end: -1;\n padding: 0;\n}\n\n.comment {\n display: grid;\n grid-template-columns: min(64px, 15%, 10vw) 1fr;\n gap: 1rem;\n overflow-wrap: break-word;\n hyphens: auto;\n}\n\n.comment-body {\n display: grid;\n gap: .5rem;\n color: #444;\n}\n\n.comment-meta {\n color: #767676;\n font-size: .875rem;\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.comment-body a {\n color: inherit;\n hyphens: none;\n}\n\n.comment-meta a {\n color: mediumvioletred;\n}\n\n.comment-avatar img {\n border-radius: 50%;\n width: 100%;\n object-fit: cover;\n aspect-ratio: 1;\n}\n\n@supports not (aspect-ratio: 1) {\n .comment-avatar {\n position: relative;\n height: 0;\n padding-bottom: 100%;\n }\n\n .comment-avatar img {\n position: absolute;\n height: 100%;\n }\n}\n","css_pre_processor":"scss","css_starter":"neither","css_prefix":"autoprefixer","head":"<meta name=&apos;viewport&apos; content=&apos;width=device-width, initial-scale=1&apos;>"}' /> <button class="button" type="submit" data-name="Comment Thread Component" aria-label="Open Comment Thread Component in CodePen"><span class="button__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false"> <path d="M32 10.909l-0.024-0.116-0.023-0.067c-0.013-0.032-0.024-0.067-0.040-0.1-0.004-0.024-0.020-0.045-0.027-0.067l-0.047-0.089-0.040-0.067-0.059-0.080-0.061-0.060-0.080-0.060-0.061-0.040-0.080-0.059-0.059-0.053-0.020-0.027-14.607-9.772c-0.463-0.309-1.061-0.309-1.523 0l-14.805 9.883-0.051 0.053-0.067 0.075-0.049 0.060-0.067 0.080c-0.027 0.023-0.040 0.040-0.040 0.061l-0.067 0.080-0.027 0.080c-0.027 0.013-0.027 0.053-0.040 0.093l-0.013 0.067c-0.025 0.041-0.025 0.081-0.025 0.121v9.996c0 0.059 0.004 0.12 0.013 0.18l0.013 0.061c0.007 0.040 0.013 0.080 0.027 0.115l0.020 0.067c0.013 0.036 0.021 0.071 0.036 0.1l0.029 0.067c0 0.013 0.020 0.053 0.040 0.080l0.040 0.053c0.020 0.013 0.040 0.053 0.060 0.080l0.040 0.053 0.053 0.053c0.013 0.017 0.013 0.040 0.040 0.040l0.080 0.056 0.053 0.040 0.013 0.019 14.627 9.773c0.219 0.16 0.5 0.217 0.76 0.217s0.52-0.080 0.76-0.24l14.877-9.875 0.069-0.077 0.044-0.060 0.053-0.080 0.040-0.067 0.040-0.093 0.021-0.069 0.040-0.103 0.020-0.060 0.040-0.107v-10c0-0.067 0-0.127-0.021-0.187l-0.019-0.060 0.059 0.004zM16.013 19.283l-4.867-3.253 4.867-3.256 4.867 3.253-4.867 3.253zM14.635 10.384l-5.964 3.987-4.817-3.221 10.781-7.187v6.424zM6.195 16.028l-3.443 2.307v-4.601l3.443 2.301zM8.671 17.695l5.964 3.987v6.427l-10.781-7.188 4.824-3.223v-0.005zM17.387 21.681l5.965-3.973 4.817 3.227-10.783 7.187v-6.427zM25.827 16.041l3.444-2.293v4.608l-3.444-2.307zM23.353 14.388l-5.964-3.988v-6.44l10.78 7.187-4.816 3.224z"></path> </svg></span> Open in CodePen</button> </form> <p>You may choose the &quot;Open in CodePen&quot; option to generate a new CodePen that includes the final styles created for this component. Use it as an opportunity to better explore the various updates we applied, including:</p> <ul> <li>responsive grid column sizing with <code>min</code></li> <li>flexbox for variable width content reflow</li> <li><code>overflow-wrap</code> and <code>hyphens</code> to handle content overflow</li> <li>combining units within <code>min</code> to account for various viewport sizes as well as zoom</li> </ul> </content>
</entry>
<entry>
<title>Guide to Advanced CSS Selectors - Part Two</title>
<link href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/"/>
<updated>2020-12-30T00:00:00Z</updated>
<id>https://moderncss.dev/guide-to-advanced-css-selectors-part-two/</id>
<content type="html"><p><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/">Continuing from part one</a>, this episode will focus on the advanced CSS selectors categorized as pseudo classes and pseudo elements and practical applications for each. We'll especially try to make sense of the syntax for <code>nth-child</code>.</p> <div class="heading-wrapper h2"> <h2 id="part-two-this-article">Part Two (this article):</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#part-two-this-article" aria-labelledby="part-two-this-article"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes">Pseudo classes</a> - ex: <code>:checked / :focus</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements">Pseudo elements</a> - ex: <code>::before / ::after</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources">Additional resources</a></li> </ul> <div class="heading-wrapper h3"> <h3 id="part-one">Part One:</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#part-one" aria-labelledby="part-one"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade">CSS Specificity and the Cascade</a></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector">Universal selector</a> - <code>*</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector">Attribute selector</a> - <code>[attribute]</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator">Child combinator</a> - <code>&gt;</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator">General sibling combinator</a> - <code>~</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">Adjacent sibling combinator</a> - <code>+</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="pseudo-classes">Pseudo Classes</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes" aria-labelledby="pseudo-classes"><span hidden="">#</span></a></div> <p>This is the largest category, and also the most context-dependent.</p> <p>Pseudo classes are keywords that are applied when they match the selected state or context of an element.</p> <p>These vastly increase the capabilities of CSS and enable functionality that in the past was often erroneously relegated to JavaScript.</p> <p>Some selectors are stateful:</p> <ul> <li><code>:focus</code></li> <li><code>:hover</code></li> <li><code>:visited</code></li> <li><code>:target</code></li> <li><code>:checked</code></li> </ul> <p>While others attach to the order of elements:</p> <ul> <li><code>:nth-child()</code> / <code>:nth-of-type()</code></li> <li><code>:first-child</code> / <code>:first-of-type</code></li> <li><code>:last-child</code> / <code>:last-of-type</code></li> <li><code>:only-child</code> / <code>:only-of-type</code></li> </ul> <p>Then there's the highly useful pseudo class <code>:not()</code>, the newly supported <code>:is()</code>, as well as the <code>:root</code> pseudo class that has come to the forefront as CSS custom properties (variables) have risen in support.</p> <blockquote> <p>Review the MDN docs for the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes">full list of available pseudo classes</a> including available options specific to form inputs, <code>video</code> captions, and language, as well as some currently in progress towards implementation.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-pseudo-classes">Practical Applications For Pseudo Classes</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#practical-applications-for-pseudo-classes" aria-labelledby="practical-applications-for-pseudo-classes"><span hidden="">#</span></a></div> <h4>Zebra Striped Table Rows</h4> <p>The <code>nth</code> series of selectors has endless applications. Use them for anything you want to occur in any sort of repetitive pattern using a 1-based index.</p> <p>An excellent candidate for this is zebra striping of table rows.</p> <p>The <code>nth-child</code> selector can use an integer, be defined using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-child#Functional_notation">functional notation</a>, or the keywords of <code>even</code> or <code>odd</code>. We'll use the keywords to most efficiently produce our zebra striping rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">tbody tr:nth-child(odd)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #ddd<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which will produce the following:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wjr035afx1qgkanwnuxe.png" alt="a two-column table where rows in the table body have a light grey background for every other (odd) table row background" /></p> <h4>Apply Alternating Background Colors</h4> <p>Using the functional notation for <code>nth-child</code> we can alternate through a series of background colors and ensure the pattern repeats in the defined order no matter how many elements exist. So a pattern of <code>rebeccapurple</code>, <code>darkcyan</code>, <code>lightskyblue</code> will repeat in that order.</p> <p>The way this works is to define the total number of colors - <code>3</code> - alongside <code>n</code>, which represents all positive numbers starting from 0, and which will be multiplied by the associated number, in this case, <code>3</code>. So by itself, <code>3n</code> would select the 3rd item, the 6th item, the 9th item, and so on. It would not select the first in the list because <code>3 x 0 = 0</code>.</p> <p>For our repetitive pattern, we want the first item selected to be the first color in our palette.</p> <p>So, we extend the notation to be <code>3n + (integer corresponding to color order)</code>, therefore our first color rule becomes:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li:nth-child(3n + 1)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> rebeccapurple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And this selects every third element, starting with the first one:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/a2smgktrsc6b7o182irc.png" alt="5 stacked items where the first and fourth have the background-color: rebeccapurple applied" /></p> <p>Essentially, that <code>+ [number]</code> shifts the starting index.</p> <p>To complete our pattern we define the following rules, incrementing the added number to be the order of the color in the repeating pattern:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li:nth-child(3n + 2)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> darkcyan<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li:nth-child(3n + 3)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> lightskyblue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Producing the following completed result:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNMpBjY" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>For an extended guide to <code>nth-child</code> checkout the <a href="https://css-tricks.com/useful-nth-child-recipies/">recipe reference</a> from CSS-Tricks and the <a href="https://css-tricks.com/examples/nth-child-tester/">nth-child tester</a> to explore constructing these selectors.</p> <blockquote> <p><strong>Enjoying this guide and finding some useful solutions</strong>? I'd appreciate a coffee to help keep me motivated to create more resources! I also offer front-end reviews and mentoring sessions, <a href="https://www.buymeacoffee.com/moderncss">choose an option to support me</a>.</p> </blockquote> <h4>Removing Extra Spacing From Child Elements</h4> <p>If you are not using a reset that starts all elements with zero margin, you may encounter typography elements creating extra unwanted margins that unbalance spacing within a visual container.</p> <p>Pseudo classes do not always need to be directly attached to an element, meaning we can do the following rule which attaches to <em>any</em> element that happens to be the last child of any parent and ensures it has no <code>margin-bottom</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">:last-child</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Excluding Elements From Selectors</h4> <p>Careful application of <code>:not()</code> is very useful for excluding elements from being selected.</p> <p>We explored a few uses of <code>:not()</code> within the attribute selector section, notably <code>a:not([class])</code> for targeting links that have no other classes applied.</p> <p><code>:not()</code> is excellent for use in utility frameworks or design systems to increase specificity on classes that have the potential to be applied to anything and for which there are known issues on certain combinations.</p> <p>An extended example of excluding it for classes with links is when you are adjusting contrast for text, possibly in a dark mode context, and want to apply the contrast adjustment to text links as well:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Non dark mode application */</span> <span class="token selector">a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Update text color for dark mode */</span> <span class="token selector">.dark-mode</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Extend the color update to links via `inherit` */</span> <span class="token selector">.dark-mode a:not([class])</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can also chain <code>:not()</code> selectors, so perhaps you want to create a rule for form field inputs, but not of certain types:</p> <pre class="language-css"><code class="language-css"><span class="token property">input</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"hidden"</span>]<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"radio"</span>]<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[type=<span class="token string">"checkbox"</span>]<span class="token punctuation">)</span></code></pre> <p>It's also possible to include other pseudo selectors within <code>:not()</code> such as to exclude the <code>:disabled</code> state for buttons:</p> <pre class="language-css"><code class="language-css"><span class="token property">button</span><span class="token punctuation">:</span> <span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span> disabled<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>This allows you to have tidier rules by defining a reset of <code>button</code> styles first, then <em>only</em> apply coloration styles, borders, etc to non-disabled buttons instead of <em>removing</em> those styles for <code>button:disabled</code> later.</p> <h4>Efficiently Select Groups of Elements</h4> <p>The <a href="https://caniuse.com/css-matches-pseudo">newly supported</a> <code>:is()</code> pseudo class:</p> <blockquote> <p>&quot;...takes a selector list as its argument, and selects any element that can be selected by one of the selectors in that list.&quot; - <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is">MDN docs on <code>:is()</code></a></p> </blockquote> <p>One way this can make a big impact is more compactly selecting typography elements such as:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>h1<span class="token punctuation">,</span> h2<span class="token punctuation">,</span> h3<span class="token punctuation">,</span> h4<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <p>Or to scope layout styles more succinctly, such as:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>header<span class="token punctuation">,</span> main<span class="token punctuation">,</span> footer<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <p>We can even combine <code>:is()</code> with <code>:not()</code> and really trim down our selectors, in this case selecting elements that are <em>not</em> headlines:</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span><span class="token function">is</span><span class="token punctuation">(</span>h1<span class="token punctuation">,</span>h2<span class="token punctuation">,</span>h3<span class="token punctuation">,</span>h4<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre> <blockquote> <p>To see this selector in context, check out the <a href="https://smolcss.dev/#smol-card-component">Smol Composable Card Component</a> from the ModernCSS companion project, SmolCSS.dev.</p> </blockquote> <p>For the immediate future, you'll want to include at least the <code>webkit</code> prefix version if you want to start using this selector. Due to a quirk in how browsers use selectors, you'll want to make this a unique rule separate from <code>is()</code> to avoid the browser throwing <em>both</em> rules out.</p> <pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token function">-webkit-any</span><span class="token punctuation">(</span>header<span class="token punctuation">,</span> main<span class="token punctuation">,</span> footer<span class="token punctuation">)</span> <span class="token punctuation">;</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Style the Current Anchor Linked Element</h4> <p>When an element is the target of an anchor link (document fragment identifier) - <code>https://url.com/#anchor-here</code> - you can style it using <code>:target</code>.</p> <p>I rely on anchor links for my project 11ty.Rocks, such as can be seen visiting this link to the <a href="https://11ty.rocks/#11ty-css-houdini-worklet-generator">CSS Houdini Worklet Generator</a>.</p> <p>The <code>:target</code> pseudo class should be placed on the element that contains the <code>id</code> attribute. However, you can chain it with descendent selectors to affect nested elements - maybe you want to give <code>article:target h2</code> a larger size or something like that.</p> <p>Leveraging <code>:target</code> I add a little extra message by combining with the pseudo element <code>::before</code> to help indicate to the visitor which item they were provided a link for, which appears as follows (&quot;It's me you're looking for...&quot;)</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/76zuti0pqhsih2zr8peg.png" alt="the :target style as applied to an article on 11ty.Rocks which has the message as described prior to this image" /></p> <p><strong>Bonus tip</strong>: ensure a bit of spacing prior to the top of the element on scroll by using <code>scroll-margin-top: 2em;</code> (or another value of your choosing). This should be considered a progressive enhancement, be sure to <a href="https://caniuse.com/mdn-css_properties_scroll-margin-top">review browser support for <code>scroll-margin-top</code></a>.</p> <h4>Visually Indicate Visited Archive Links</h4> <p>The <code>:visited</code> pseudo class is very unique because of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector">the potential to be exploited in terms of user's privacy</a>. To resolve this, browser makers have limited which CSS styles are allowed to be applied using <code>:visited</code>.</p> <p>Una Kravets has a much more <a href="https://una.im/hacking-visited/">in-depth reference exploring how to create useful <code>:visited</code> styles</a>, but here's the reduced version which I have in use <a href="https://stylestage.dev/styles/">for visitors of Style Stage to track which styles they have already viewed</a>.</p> <p>A key gotcha is that styles applied via <code>:visited</code> will always use the parent's alpha channel - meaning, you cannot use <code>rgba</code> to go from invisible to visible, you must change the whole color value.</p> <p>So, to hide the initial state, you need to be able to use a solid color, such as the page background color.</p> <p>Additionally, for accessibility, it may not be desirable for the pseudo element content to be read out if it's an icon or emoji since we cannot supply an accessible name for <code>content</code> values. There is inconsistencies between assistive technology in whether pseudo element content is read, so we can try to ensure it's ignored with the use of <code>aria-hidden=&quot;true&quot;</code>.</p> <p>Our first step then is to add a span within links and that is what we will ultimately apply the <code>:visited</code> stylings to:</p> <pre class="language-html"><code class="language-html"><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 punctuation">></span></span>Article Title <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>The default styling (non-visited) adds the pseudo element, and makes the color the same as the page background to hide it visually:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a span[aria-hidden]::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">"✔"</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-background<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, when the link has been visited, we update the color to make it visible:</p> <pre class="language-css"><code class="language-css"><span class="token selector">a:visited span[aria-hidden]::after</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Advanced Interactions With <code>:focus-within</code></h4> <p>An up and coming pseudo class is <code>:focus-within</code> for which a polyfill is available, but otherwise it should be used with caution or as a progressive enhancement.</p> <p>The <code>:focus-within</code> pseudo class as described by <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">MDN docs:</a></p> <blockquote> <p>The <code>:focus-within</code> CSS pseudo-class represents an element that has received focus or contains an element that has received focus. In other words, it represents an element that is itself matched by the <code>:focus</code> pseudo-class or has a descendant that is matched by <code>:focus</code>.</p> </blockquote> <p>For a practical way to use <code>:focus-within</code> review the tutorial for a <a href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/">CSS-Only Accessible Dropdown Navigation Menu</a>.</p> <div class="heading-wrapper h2"> <h2 id="pseudo-elements">Pseudo Elements</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements" aria-labelledby="pseudo-elements"><span hidden="">#</span></a></div> <p>Pseudo elements allow you to style a specific part of the selected element. They vary quite widely in application, with the (currently) best supported ones being the following:</p> <ul> <li><code>::after</code></li> <li><code>::before</code></li> <li><code>::first-letter</code></li> <li><code>::first-line</code></li> <li><code>::selection</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-pseudo-elements">Practical Applications For Pseudo Elements</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#practical-applications-for-pseudo-elements" aria-labelledby="practical-applications-for-pseudo-elements"><span hidden="">#</span></a></div> <h4>Extra Visual Elements For Styling Benefits</h4> <p>The <code>::before</code> and <code>::after</code> pseudo elements create an additional element that visually appears to be part of the DOM, but is not part of the real HTML DOM. They are as fully style-able as any real DOM element.</p> <p>I have used these elements for all sorts of embellishments. Since they act like real elements, they are computed as child elements when using flexbox or CSS grid layout, which has greatly increased their functionality in my toolbox.</p> <p>A few key concepts for using <code>::before</code> and <code>::after</code>:</p> <ul> <li>Requires the <code>content</code> property before being made visible, but this property can be set to a blank string - <code>content: &quot;&quot;;</code></li> <li>Critical text content should not be included in the <code>content</code> value since it's inconsistently accessed by assistive technology</li> <li>Unless positioned otherwise, <code>::before</code> will display prior to the main element content, and <code>::after</code> will display after it.</li> </ul> <p>Here's a demo of the default behavior of these with just a little bit of styling applied:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/h66jvp0e6svmbbw09sh1.png" alt="A short paragraph showing the the text of &quot;Before&quot; in sitting prior to the paragraph content and the text of &quot;After&quot; sitting after the paragraph content" /></p> <p>Notice they act like inline elements by default, and follow the wrapping behavior as well for longer content:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o01fjpavgllxqlcbubrk.png" alt="A slightly longer paragraph that has wrapping lines showing the the text of &quot;Before&quot; in sitting prior to the paragraph content and the text of &quot;After&quot; sitting after the paragraph content" /></p> <p>And here's with the singular adjustment to add <code>display: flex</code> to the paragraph:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/15693mr4uufc3hhxdbor.png" alt="The same multiline paragraph but the &quot;Before&quot; appears as a column to the left of the paragraph content, and &quot;After&quot; appears as a column after the paragraph content" /></p> <p>And here's with swapping that to <code>display: grid</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/n3trrnffml62n7oudlsl.png" alt="The same multiline paragraph but the &quot;Before&quot; appears as a row on top of the paragraph content, and &quot;After&quot; appears as a row below the paragraph content" /></p> <p>The <code>::before</code> and <code>::after</code> elements are quick ways to add simple, consistent typography flourishes, a few of which can be seen in this CodePen demo:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="PoGEwzB" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>Did you catch the trick in the emoji one?</p> <p>We can retrieve the value of any attribute on the element to use in the <code>content</code> property via the <code>attr()</code> function:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* &lt;h2 class="emoji" data-emoji="😍"> */</span> <span class="token selector">.emoji::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-emoji<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a gist of <a href="https://gist.github.com/5t3ph/d44a1677d68cf86eb0683d5050a84692">how to display element <code>id</code> and <code>class</code> values in pseudo elements</a> using this same idea.</p> <h4>Emphasize an Article Lead</h4> <p>The &quot;lede&quot; (pronounced &quot;lead&quot;) is a newsprint term for the first paragraph within a news article and is intended to be a summary of the key point of the article (you may have heard the phrase &quot;Don't bury the lede!&quot;).</p> <p>We can combine the pseudo class of <code>:first-of-type</code> with the pseudo element of <code>:first-line</code> to emphasize the first line of paragraph copy. Interestingly, this is dynamic and will change as the viewport size changes.</p> <pre class="language-css"><code class="language-css"><span class="token selector">article p:first-of-type:first-line</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.1em<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> darkcyan<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Producing the following inherently responsive result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/50ym2bam1ic7frl913fh.gif" alt="gif demo of resizing the viewport when the previously described rule is applied and seeing that as the number of words in the first line of the paragraph changes the style continues to only affect the very first line" /></p> <h4>Ensure Accessible Contrast For Text Selection</h4> <p>A frequently missed style is for text selection, despite it being an interaction many of us engage in multiple times a day.</p> <p>While browsers try to handle styling this event, it is possible to lose contrast. I encountered this when styling <a href="http://moderncss.dev/">ModernCSS.dev</a> due to the darker theme in use.</p> <p>To resolve, we can use the <code>::selection</code> pseudo element to supply a custom text color and background color:</p> <pre class="language-css"><code class="language-css"><span class="token selector">::selection</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> yellow<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Custom List Bullet Styles</h4> <p>An up and coming pseudo element specifically for styling list bullets is <code>::marker</code>.</p> <p>For the browser support link and an example of how to use it, check out the section in my tutorial on <a href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker">Totally Custom List Styles</a>.</p> <div class="heading-wrapper h2"> <h2 id="additional-resources">Additional Resources</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources" aria-labelledby="additional-resources"><span hidden="">#</span></a></div> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors">MDN Docs</a> on CSS selectors</li> <li><a href="https://hugogiraudel.github.io/selectors-explained/">Selectors Explained</a> - enter any selector to learn what is being affected</li> <li><a href="https://polypane.app/css-specificity-calculator/">CSS specificity calculator by Polypane</a> - discover the level of specificity of a selector</li> <li><a href="https://flukeout.github.io/">CSS Diner</a> - a game to test your ability to create CSS selectors</li> <li><a href="https://stylestage.dev/">Style Stage</a> - a great place to practice your new selector knowledge is my other project, <a href="https://stylestage.dev/">Style Stage</a>, which is a modern CSS showcase styled by community contributions. A limitation is the inability to add new classes or IDs, so you will need to exercise your selector abilities to successfully create your stylesheet submission!</li> </ul> <blockquote> <p><strong><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/">Head back to part one to learn about five other categories of advanced CSS selectors</a></strong>. And, if you learned something from this guide and you're able, <a href="https://www.buymeacoffee.com/moderncss">I'd appreciate a coffee</a> to help me bring you more tutorials and resources!</p> </blockquote> </content>
</entry>
<entry>
<title>Guide to Advanced CSS Selectors - Part One</title>
<link href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/"/>
<updated>2020-12-28T00:00:00Z</updated>
<id>https://moderncss.dev/guide-to-advanced-css-selectors-part-one/</id>
<content type="html"><p>Whether you choose to completely write your own CSS, or use a framework, or be required to build within a design system - understanding selectors, the cascade, and specificity are critical to developing CSS and modifying existing style rules.</p> <p>You're probably quite familiar with creating CSS selectors based on IDs, classes, and element types. And you've likely often used the humble space character to select descendants.</p> <p>In this two-part mini-series, we'll explore some of the more advanced CSS selectors, and examples of when to use them.</p> <div class="heading-wrapper h2"> <h2 id="part-one-this-article">Part One (this article):</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#part-one-this-article" aria-labelledby="part-one-this-article"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade">CSS Specificity and the Cascade</a></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector">Universal selector</a> - <code>*</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector">Attribute selector</a> - <code>[attribute]</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator">Child combinator</a> - <code>&gt;</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator">General sibling combinator</a> - <code>~</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">Adjacent sibling combinator</a> - <code>+</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="part-two">Part Two:</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#part-two" aria-labelledby="part-two"><span hidden="">#</span></a></div> <ul> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-classes">Pseudo classes</a> - ex: <code>:checked / :focus</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#pseudo-elements">Pseudo elements</a> - ex: <code>::before / ::after</code></li> <li><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/#additional-resources">Additional resources</a></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="css-specificity-and-the-cascade">CSS Specificity and the Cascade</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#css-specificity-and-the-cascade" aria-labelledby="css-specificity-and-the-cascade"><span hidden="">#</span></a></div> <p>A key concept to successfully setting up CSS selectors is understanding what is known as CSS specificity, and the &quot;C&quot; in CSS, which is the cascade.</p> <blockquote> <p>Specificity is a weight that is applied to a given CSS declaration, determined by the number of each selector type in the matching selector. When multiple declarations have equal specificity, the last declaration found in the CSS is applied to the element. Specificity only applies when the same element is targeted by multiple declarations. As per CSS rules, directly targeted elements will always take precedence over rules which an element inherits from its ancestor. - <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">MDN docs</a></p> </blockquote> <p>Proper use of the cascade and selector specificity means you should be able to entirely avoid the use of <code>!important</code> in your stylesheets.</p> <p>Increasing specificity comes with the result of overriding inheritance from the cascade.</p> <p>As a small example - what color will the <code>.item</code> be?</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</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>specific<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>item<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <pre class="language-css"><code class="language-css"><span class="token selector">#specific .item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">span.item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.item</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>.item</code> will be <em>red</em> because the specificity of including the <code>#id</code> in the selector wins against the cascade and over the element selector.</p> <p>This doesn't mean to go adding <code>#ids</code> to all your elements and selectors, but rather to be aware of their impact on specificity.</p> <blockquote> <p><strong>Key concept</strong>: The higher the specificity, the more difficult to override the rule.</p> </blockquote> <p>Every project will be unique in its needs in terms of reusability of rules. The desire to share rules with low specificity has lead to the rise of CSS utility-driven frameworks such as Tailwind and Bulma.</p> <p>On the other hand, the desire to tightly control inheritance and specificity such as within a design system makes naming conventions like BEM popular. In those systems, a parent selector is tightly coupled with child selectors to create reusable components that create their own specificity bubble.</p> <p>If you're thinking &quot;I don't need to learn these because I use a framework/design system&quot; then you are greatly limiting yourself in terms of using CSS to its fullest extent.</p> <p>The beauty of the language can be found in constructing elegant selectors that do <em>just enough</em> and enable tidy small stylesheets.</p> <div class="heading-wrapper h2"> <h2 id="universal-selector">Universal Selector</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#universal-selector" aria-labelledby="universal-selector"><span hidden="">#</span></a></div> <p>The universal selector - <code>*</code> - is so named because it applies to all elements universally.</p> <p>There used to be recommendations against using it, particularly as a descendent, because of performance concerns, but that is no longer a valid consideration. In fact, it hasn't been a concern in over a decade. Instead, worry about your JS bundle size and ensuring your images are optimized rather than finessing CSS selectors for performance reasons 😉</p> <p>A better reason to only use it sparingly is that it has zero specificity when used by itself, meaning it can be overridden by a single id, class, or element selector.</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-universal-selector">Practical Applications For the Universal Selector</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-universal-selector" aria-labelledby="practical-applications-for-the-universal-selector"><span hidden="">#</span></a></div> <h4>CSS Box Model Reset</h4> <p>My most common usage is as my very first CSS reset rule:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*, *::before, *::after</span> <span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This means that we want all elements to <em>include</em> padding and borders in the box model calculation instead of <em>adding</em> those widths to any defined dimensions. For example, in the following rule, the <code>.box</code> will be <code>200px</code> wide, not <code>200px + 20px</code> from the padding:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 200px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Vertical Rhythm</h4> <p>Another very useful application was recommended by Andy Bell and Heydon Pickering in their <a href="https://every-layout.dev/">Every Layout</a> site and book and is called &quot;<a href="https://every-layout.dev/layouts/stack/">The Stack</a>&quot; which in it's most simple form looks like this:</p> <pre class="language-css"><code class="language-css"><span class="token selector">* + *</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>When used with a reset or parent rule that reduces all element margins to zero, this applies a top margin to all elements <em>that follow another element</em>. This is a quick way to gain vertical rhythm.</p> <p>If you do want to be a little more - well, selective - then I enjoy using it as a descendent in specific circumstances such as the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">article * + h2</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 4rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is similar to the stack idea, but more targeted towards the headline elements to provide a bit more breathing room between content sections.</p> <div class="heading-wrapper h2"> <h2 id="attribute-selector">Attribute Selector</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#attribute-selector" aria-labelledby="attribute-selector"><span hidden="">#</span></a></div> <p>This is an exceedingly powerful category and yet often not used to its full potential.</p> <p>Did you know you can achieve matching results similar to regex by leveraging CSS attribute selectors?</p> <p>This is exceptionally useful for modifying BEM styled systems or other frameworks that use related class names but perhaps not a single common class name.</p> <p>Let's see an example:</p> <pre class="language-css"><code class="language-css">[class*=<span class="token string">"component_"</span>]</code></pre> <p>This selector will select all elements which have a class that <em>contains</em> the string of &quot;component_&quot;, meaning it will match &quot;component_title&quot; and &quot;component_content&quot;.</p> <p>And you can ensure the match is case insensitive by including <code>i</code> prior to closing the attribute selector:</p> <pre class="language-css"><code class="language-css">[class*=<span class="token string">"component_"</span> i]</code></pre> <p>But you don't have to specify an attribute value, you can simply check if it's present, such as:</p> <pre class="language-css"><code class="language-css">a[class]</code></pre> <p>Which would select <em>all</em> <code>a</code> (link elements) that have <em>any</em> class value.</p> <blockquote> <p>Review the MDN docs for <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">all the possible ways to match values within attribute selectors</a>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-attribute-selectors">Practical Applications For Attribute Selectors</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-attribute-selectors" aria-labelledby="practical-applications-for-attribute-selectors"><span hidden="">#</span></a></div> <h4>Assist in Accessibility Linting</h4> <p>These selectors can be wielded to perform some basic accessibility linting, such as the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">img:not([alt])</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This will add an outline to all images that do not include an <code>alt</code> attribute.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Attach to Aria to Enforce Accessibility</h4> <p>Attribute selectors can also help enforce accessibility if they are used as the <em>only</em> selector, making the absence of the attribute prevent the associated styling. One way to do this is by attaching to required <code>aria</code> attributes</p> <p>One example is when implementing an accordion interaction where you need to include the following button, whether the aria boolean is toggled via JavaScript:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</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 punctuation">></span></span>Toggle<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <p>The related CSS could then use the <code>aria-expanded</code> as an attribute selector alongside the adjacent sibling combinator to style the related content open or closed:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button[aria-expanded="false"] + .content</span> <span class="token punctuation">{</span> <span class="token comment">/* hidden styles */</span> <span class="token punctuation">}</span> <span class="token selector">button[aria-expanded="true"] + .content</span> <span class="token punctuation">{</span> <span class="token comment">/* visible styles */</span> <span class="token punctuation">}</span></code></pre> <h4>Styling Non-Button Navigation Links</h4> <p>When dealing with navigation, you may have a mix of default links and links stylized as &quot;buttons&quot;. In this case, it can be very useful to use the following to select non-&quot;button&quot; links:</p> <pre class="language-css"><code class="language-css">nav <span class="token property">a</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>[class]<span class="token punctuation">)</span></code></pre> <h4>Remove Default List Styling</h4> <p>Another tip I've started incorporating from Andy Bell and his <a href="https://piccalil.li/blog/a-modern-css-reset/">modern CSS reset</a> is to remove list styling based on the presence of the <code>role</code> attribute:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */</span> <span class="token selector">ul[role="list"], ol[role="list"]</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="child-combinator">Child Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#child-combinator" aria-labelledby="child-combinator"><span hidden="">#</span></a></div> <p>The child combinator selector - <code>&gt;</code> - is very effective at adding just a bit of specificity to reduce scope when applying styles to element descendants. It is the only selector that deals with <em>levels</em> of elements and can be compounded to select nested elements.</p> <p>The child combinator scopes descendent styling from <em>any</em> descendent that matches the child selector to only <em>direct descendants</em>.</p> <p>In other words, whereas <code>article p</code> selects <em>all</em> <code>p</code> within <code>article</code>, <code>article &gt; p</code> selects only paragraphs that are directly within the article, not nested within other elements.</p> <p>✅ Selected with <code>article &gt; p</code></p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>🚫 Not selected with <code>article &gt; p</code></p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>blockquote</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Hello world<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>blockquote</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-child-combinator">Practical Applications For the Child Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-child-combinator" aria-labelledby="practical-applications-for-the-child-combinator"><span hidden="">#</span></a></div> <h4>Nested Navigation List Links</h4> <p>Consider a sidebar navigation list, such as for a documentation site, where there are nested levels of links. Semantically, this means an outer <code>ul</code> and also nested <code>ul</code> within <code>li</code>.</p> <p>For visual hierarchy, you likely want to style the top-level links differently than the nested links. To target only the top-level links, you can use the following:</p> <pre class="language-css"><code class="language-css"><span class="token selector">nav > ul > li > a</span> <span class="token punctuation">{</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a CodePen where you can <a href="https://codepen.io/5t3ph/pen/dypZwJd">experiment with what happens if you remove any of the child combinators</a> in that selector.</p> <h4>Scoping Element Selectors</h4> <p>I enjoy using element selectors for the foundational things in my page layouts, such as <code>header</code> or <code>footer</code>. But you can get into trouble since those elements are valid children of certain other elements, such as <code>footer</code> within a <code>blockquote</code> or an <code>article</code>.</p> <p>In this case, you may want to adjust from <code>footer</code> to <code>body &gt; footer</code>.</p> <h4>Styling Embedded / Third-Party Content</h4> <p>Sometimes you truly do not have control over class names, IDs, or even markup. For example, for ads or other JavaScript-driven (non-iframe) content.</p> <p>In this case, you may be faced with a sea of divs or spans, in which case the child combinator can be very useful for attaching styles to varying levels of content.</p> <blockquote> <p>Note that many of the other selectors discussed can help in this scenario as well, but only the child combinator deals with <em>levels</em> and can affect nested elements.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="general-sibling-combinator">General Sibling Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#general-sibling-combinator" aria-labelledby="general-sibling-combinator"><span hidden="">#</span></a></div> <p>The general sibling combinator - <code>~</code> - selects the defined elements that are located <em>somewhere after</em> the preceding (prior defined) element and that are <em>within the same parent</em>.</p> <p>For example, <code>p ~ img</code> would style all images that are located <em>somewhere after</em> a paragraph provided they share the same parent.</p> <p>This means all the following images would be selected:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Paragraph<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Headline 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Headline 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>But <strong>not</strong> the image in this scenario:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>img.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Image<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Paragraph<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>It is likely you would want to be a bit more specific (see also: the adjacent sibling combinator), and this selector tends to be used most in creative coding exercises, such as my <a href="https://codepen.io/5t3ph/pen/ExPVEZP">CommitSweeper pure CSS game</a>.</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-general-sibling-combinator">Practical Applications For the General Sibling Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-general-sibling-combinator" aria-labelledby="practical-applications-for-the-general-sibling-combinator"><span hidden="">#</span></a></div> <h4>Visual Indication of A State Change</h4> <p>Combining the general sibling combinator with stateful pseudo class selectors, such as <code>:checked</code>, can produce interesting results.</p> <p>Given the following HTML for a checkbox:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>terms<span class="token punctuation">"</span></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>checkbox<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>terms<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>I accept the terms<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- series of &lt;p> with the terms content --></span></code></pre> <p>We can alter the style of the terms paragraphs using the general sibling combinator <em>only when</em> the checkbox has been checked:</p> <pre class="language-css"><code class="language-css"><span class="token selector">#terms:checked ~ p</span> <span class="token punctuation">{</span> <span class="token property">font-style</span><span class="token punctuation">:</span> italic<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #797979<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <h4>Low Specificity Variations</h4> <p>If we also pull in the universal selector, we can quickly generate slight variations such as for simple card layouts.</p> <p>Rather than moving content around and in and out of nested divs with classes to alter the arrangement of the headline and paragraphs, we can use the general sibling combinator instead to produce the following variations.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/g4e3fw68cot36qrb9k1p.png" alt="Three card layouts including a headline, two paragraphs, and an image. Any content that follows an image gains the style described below." /></p> <p>The rule adds some margin, reduces the font size and lightens the text color for any element that follows the image:</p> <pre class="language-css"><code class="language-css"><span class="token selector">img ~ *</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.9rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #797979<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem 1rem 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You can <a href="https://codepen.io/5t3ph/pen/VwKrqVB">experiment with these general sibling combinator results in this CodePen</a>.</p> <p>This rule has extremely low specificity, so you could easily override it by adding a more targeted rule.</p> <p>Additionally, since it only applies when the elements are shared direct descendants of the image's parent - a <code>li</code> in this case - once you wrap the content in another element the rule will only apply up until inheritance is in use by child elements. To better understand this, try wrapping the content in the last card item in a div. The color and margin will be inherited on the <code>div</code> and type elements, but the native browser styling on the <code>h3</code> prevents the <code>font-size</code> from the general sibling combinator from being inherited since the native browser rule has higher specificity than the universal selector which is technically targeting the <code>div</code>.</p> <div class="heading-wrapper h2"> <h2 id="adjacent-sibling-combinator">Adjacent Sibling Combinator</h2> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator" aria-labelledby="adjacent-sibling-combinator"><span hidden="">#</span></a></div> <p>The adjacent sibling combinator - <code>+</code> - selects the element that directly follows the preceding (prior defined) element.</p> <p>We've already used this within the universal selector examples - <code>* + *</code> - to apply a top margin to only elements that follow another element - so let's get right to more examples!</p> <div class="heading-wrapper h3"> <h3 id="practical-applications-for-the-adjacent-sibling-combinator">Practical Applications For the Adjacent Sibling Combinator</h3> <a class="anchor" href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#practical-applications-for-the-adjacent-sibling-combinator" aria-labelledby="practical-applications-for-the-adjacent-sibling-combinator"><span hidden="">#</span></a></div> <h4>Polyfill For Lack fo Flexbox Gap Support in Navigation</h4> <p><a href="https://caniuse.com/flexbox-gap">Flexbox gap support</a> is on the way, but at the time of writing is notably not yet available in Safari outside of their technology preview.</p> <p>So, in the case of something like website navigation where flex layout is very useful, we can use the adjacent sibling combinator to assist in adding margin as a polyfill for <code>gap</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">nav ul li + li</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which enables a gap effect between list items without needing to clear out an extra margin on the first:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/q92z4bj0yhirnp5w9exh.png" alt="Result of the previously described rule where the flex inlined link items have space between them provided by margin-left" /></p> <h4>Applying Spacing Between Form Labels and Inputs</h4> <p>The theory being applied in &quot;the stack&quot; that we explored for universal selectors is to only apply margin in one direction.</p> <p>We can use this idea scoped to form field objects to provide enough spacing to retain visual hierarchy between field types. In this case, we add a much smaller margin between a label and it's input, and a larger margin between inputs and labels:</p> <pre class="language-css"><code class="language-css"><span class="token selector">label + input</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input + label</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>Note</em>: This example works in a limited context. You probably want to enclose field types with a grouping element to ensure consistency between field types, vs. list all field types besides <code>input</code> such as <code>select</code> and <code>textarea</code>. If forms design is of interest to you, check out the mini-series here on ModernCSS and stay tuned for my upcoming <a href="https://5t3ph.dev/egghead">egghead course</a> about cross-browser form field styling.</p> <blockquote> <p><strong><a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-two/">Continue to part two where we'll learn about pseudo classes and pseudo elements</a></strong>. A great place to practice your new selector knowledge is my other project, <a href="https://stylestage.dev/">Style Stage</a> - a modern CSS showcase styled by community contributions. And, if you learned something from this guide and you're able, <a href="https://www.buymeacoffee.com/moderncss">I'd appreciate a coffee</a> to help me bring you more tutorials and resources!</p> </blockquote> </content>
</entry>
<entry>
<title>The 3 CSS Methods for Adding Element Borders</title>
<link href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/"/>
<updated>2020-12-11T00:00:00Z</updated>
<id>https://moderncss.dev/the-3-css-methods-for-adding-element-borders/</id>
<content type="html"><p>When it comes to CSS, sometimes a <code>border</code> is not really a <code>border</code>.</p> <p>In this episode, we'll cover the differences between:</p> <ul> <li><code>border</code></li> <li><code>outline</code></li> <li><code>box-shadow</code></li> </ul> <p>We'll also discuss when you might use one over the other.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="refresher-on-the-css-box-model">Refresher on the CSS Box Model</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#refresher-on-the-css-box-model" aria-labelledby="refresher-on-the-css-box-model"><span hidden="">#</span></a></div> <p>A key difference between our three border methods is <em>where</em> they are placed on an element and <em>how</em> they affect its dimensions. This behavior is controlled by the CSS box model.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4f0uy1wh7h3brj8361h4.png" alt="An illustration of the CSS box model, with the relevant parts described following this image" /></p> <ul> <li>the <code>border</code> is precisely the boundary of the element, sitting between its padding and margin, and it's width will impact the computed element dimensions</li> <li>the <code>outline</code> is next to but outside of the <code>border</code>, overlapping both <code>box-shadow</code> and <code>margin</code>, but not affecting the element's dimensions</li> <li>by default, <code>box-shadow</code> extends out from edge of the border covering the amount of space in the direction(s) defined, and it will also not affect the element's dimensions</li> </ul> <div class="heading-wrapper h2"> <h2 id="border-syntax-and-usage"><code>border</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#border-syntax-and-usage" aria-labelledby="border-syntax-and-usage"><span hidden="">#</span></a></div> <p>Borders have been a standard in design since the beginning of the web.</p> <p>An important difference compared to the other two methods we're going to cover is that <em>by default</em> borders are included in the computed dimensions of an element. Even if you set <code>box-sizing: border-box</code> the borders still figure into the calculation.</p> <p>The most essential syntax for a border defines it's width and style:</p> <pre class="language-css"><code class="language-css"><span class="token property">border</span><span class="token punctuation">:</span> 3px solid<span class="token punctuation">;</span></code></pre> <p>If not defined, the default color will be <code>currentColor</code> which means it will use the nearest definition for <code>color</code> in the cascade.</p> <p>But there are more styles available for border, and using <code>border-style</code> you can define a different style for each side if you'd like.</p> <blockquote> <p>Visit <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-style">MDN docs</a> to review all available <code>border-style</code> values and see examples.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use-border">When to Use <code>border</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-border" aria-labelledby="when-to-use-border"><span hidden="">#</span></a></div> <p>Border is a solid choice (pun intended) for when it's acceptable for the style to be computed in the element's dimensions. And the default styles give a lot of design flexibility.</p> <blockquote> <p>Keep reading for a <strong>bonus tip</strong> about something only <code>border</code> can do!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="outline-syntax-and-usage"><code>outline</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#outline-syntax-and-usage" aria-labelledby="outline-syntax-and-usage"><span hidden="">#</span></a></div> <p>For outlines, the only required property to make it visible is to provide a style since the default is <code>none</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">outline</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span></code></pre> <p>Like border, it will gain color via <code>currentColor</code> and it's default width will be <code>medium</code>.</p> <p>The typical application of <code>outline</code> is by native browser styles on <code>:focus</code> of interactive elements like links and buttons.</p> <p>This particular application should <em>be allowed to occur</em> for purposes of accessibility unless you are able to provide a custom <code>:focus</code> style that meets the <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html">WCAG Success Criterion 2.4.7 Focus Visible</a>.</p> <p>For design purposes, an often noted issue with <code>outline</code> is that it is unable to inherit the curve from any <code>border-radius</code> styles.</p> <div class="heading-wrapper h3"> <h3 id="when-to-use-outline">When to Use <code>outline</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-outline" aria-labelledby="when-to-use-outline"><span hidden="">#</span></a></div> <p>Use of <code>outline</code> may be desirable when you don't want to impact the element's dimensions, and you don't need it to follow a <code>border-radius.</code> It happens to use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/outline-style">the same style values as border</a> so you can achieve a similar look.</p> <div class="heading-wrapper h2"> <h2 id="box-shadow-syntax-and-usage"><code>box-shadow</code> Syntax and Usage</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#box-shadow-syntax-and-usage" aria-labelledby="box-shadow-syntax-and-usage"><span hidden="">#</span></a></div> <p>The minimal required properties for <code>box shadow</code> are values for the <code>x</code> and <code>y</code> axis and a color:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 5px 8px black<span class="token punctuation">;</span></code></pre> <p>Optionally, add a third unit to create <code>blur</code>, and a fourth to add <code>spread</code>.</p> <blockquote> <p>Check out <a href="https://5t3ph.dev/box-shadow">my 4.5 minute video demo on egghead</a> to learn more about the expanded syntax as well as tips on using <code>box-shadow</code>.</p> </blockquote> <p>To use it to create a border, we set the <code>x</code> and <code>y</code> axis values as well as the <code>blur</code> to <code>0</code>. Then set a positive number for <code>spread</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px blue<span class="token punctuation">;</span></code></pre> <p>This will create the appearance of a border around the element <em>and</em> it can even follow an applied <code>border-radius</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/fcym33w4yzwxjqwpqu0v.png" alt="an element using box-shadow in place of a border for the effect of a rounded blue border" /></p> <div class="heading-wrapper h3"> <h3 id="when-to-use-box-shadow">When to Use <code>box-shadow</code></h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#when-to-use-box-shadow" aria-labelledby="when-to-use-box-shadow"><span hidden="">#</span></a></div> <p>You may prefer <code>box-shadow</code> particularly when you want to animate a border without causing layout shift. The next section will demonstrate an example of this context.</p> <p>And since <code>box-shadow</code> can be layered, it's an all-around good tool to get to know to enhance your layouts.</p> <p>However, it will not be able to fully mimic some of the styles provided by <code>border</code> and <code>outline</code>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="putting-it-all-together">Putting It All Together</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#putting-it-all-together" aria-labelledby="putting-it-all-together"><span hidden="">#</span></a></div> <p>Here are a few practical scenarios where you may need to use a <code>border</code> alternative.</p> <div class="heading-wrapper h3"> <h3 id="button-borders">Button Borders</h3> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#button-borders" aria-labelledby="button-borders"><span hidden="">#</span></a></div> <p>A common case of the real <code>border</code> becoming an issue is when providing styles for both bordered and non-bordered buttons, and the scenario of them lining up next to each other.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0bttmgk48k3vb3dygjr6.png" alt="a button using a border which appears visually larger than the second button with a background but no border" /></p> <p>A typical solution is usually increasing the non-bordered button dimensions equal to the <code>border-width</code>.</p> <p>An alternate solution with our new knowledge is to use <code>box-shadow</code> along with the <code>inset</code> keyword to place the pseudo border visually <em>inside</em> the button:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/r1phaj5fbfqute0b5gri.png" alt="updated styles using box-shadow on the first button resulting in the buttons appearing equal size" /></p> <p><em>Note that your padding will have to be larger than the <code>border-width</code> to prevent overlap of the text content</em>.</p> <p>Alternatively, perhaps you want to <em>add</em> a border on <code>:hover</code> or <code>:focus</code>. Using the real <code>border</code>, you will have an undesirable visual jump from layout shift since the <code>border</code> will briefly increase the dimensions in those states.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v5ayfxarp501kawc5tre.gif" alt="demo of a border being added on hover and causing the button to jump in place" /></p> <p>In this case, we can use <code>box-shadow</code> to create the pseudo border so that the true dimensions are not increased - plus we can animate it using <code>transition</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5x1yluygxigo1uan1p5r.gif" alt="demo of the box-shadow border on hover which no longer causes the button to jump" /></p> <p>Here's the reduced code for the above example:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 180ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:hover</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px tomato<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="upgrading-your-css-debugging-method">Upgrading Your CSS Debugging Method</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#upgrading-your-css-debugging-method" aria-labelledby="upgrading-your-css-debugging-method"><span hidden="">#</span></a></div> <p>A classic CSS joke is that to figure out CSS issues particularly for overflow scrolling or positioning is to add:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which will add a red border to every element.</p> <p>But as we've learned, this will also affect their computed dimensions, thus <em>potentially</em> accidentally causing you additional issues.</p> <p>Instead, use:</p> <pre class="language-css"><code class="language-css"><span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><em>Pop quiz</em>: where will the <code>outline</code> be placed, and why is this a better solution?</p> </blockquote> <p>One potential consequence of using <code>border</code> is <em>adding</em> scrollbars once content is re-drawn. This side-effect will not happen when using <code>outline</code>.</p> <p>Additionally, you're likely to be using <code>border</code> for elements already, so universally changing the <code>border</code> will cause layout shifts which is certainly likely to introduce new problems.</p> <blockquote> <p><em>Side note</em>: Browser DevTools also have evolved more sophisticated methods of helping you identify elements, with <a href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Debug_Scrollable_Overflow">Firefox</a> even adding both a &quot;scroll&quot; and &quot;overflow&quot; tag that is helpful in the case of debugging for overflow. I encourage you to spend some time learning more about DevTool features!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="ensuring-visible-focus">Ensuring Visible Focus</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#ensuring-visible-focus" aria-labelledby="ensuring-visible-focus"><span hidden="">#</span></a></div> <p>For accessibility, one scenario you may not be aware of is users of Windows High Contrast Mode (WHCM).</p> <p>In this mode, your provided colors are stripped away to a reduced high contrast palette. Not all CSS properties are allowed, including <code>box-shadow</code>.</p> <p>One practical impact is that if you have removed <code>outline</code> upon <code>:focus</code> and replaced it with <code>box-shadow</code>, users of WHCM will no longer be given visible focus.</p> <p>To remove this negative impact, you can apply a <code>transparent</code> outline on <code>:focus</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">button:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>For a bit more context on this specific issue, review <a href="https://moderncss.dev/css-button-styling-guide/#focus">the episode on button styling</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="pitfalls-of-outline-and-box-shadow">Pitfalls of <code>outline</code> and <code>box-shadow</code></h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#pitfalls-of-outline-and-box-shadow" aria-labelledby="pitfalls-of-outline-and-box-shadow"><span hidden="">#</span></a></div> <p>Because <code>outline</code> and <code>box-shadow</code> sit <em>outside</em> of the border in the box model, one consequence you may encounter is having them disappear under the edges of the viewport. So, you may need to add <code>margin</code> to the element or <code>padding</code> to the <code>body</code> as a countermeasure if you want it to remain visible.</p> <p>Their placement also means they can be sheared off by properties such as <code>overflow: hidden</code> or the use of <code>clip-path</code>.</p> <div class="heading-wrapper h2"> <h2 id="bonus-tip-gradient-borders">Bonus Tip: Gradient Borders</h2> <a class="anchor" href="https://moderncss.dev/the-3-css-methods-for-adding-element-borders/#bonus-tip-gradient-borders" aria-labelledby="bonus-tip-gradient-borders"><span hidden="">#</span></a></div> <p>As promised, here's a bonus tip about something that - of the methods we've discussed - only <code>border</code> can do.</p> <p>An often forgotten border-related property is <code>border-image</code>. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-image">The syntax can be a bit strange</a> when trying to use it with actual images.</p> <p>But it has a hidden power - it also allows you to create gradient borders since CSS gradients are technically images:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jczxtsix50q9exeprh33.png" alt="preview of an element that has a pastel rainbow gradient applied with the text &quot;Hello World&quot;" /></p> <p>This requires defining a regular border width and style (although it will only display as <code>solid</code> regardless of style value), followed by the <code>border-image</code> property that can use the gradient syntax with one addition. The number after the gradient is the <code>slice</code> value which for our gradient we can simply use a value of <code>1</code> to essentially not alter the sizing/distortion of the gradient.</p> <pre class="language-css"><code class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid<span class="token punctuation">;</span> <span class="token comment">/* simplified from preview image */</span> <span class="token property">border-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to right<span class="token punctuation">,</span> purple<span class="token punctuation">,</span> pink<span class="token punctuation">)</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>To place the border on only one side, be sure to set the other sides to zero first or some browsers will still add it to all sides:</p> <pre class="language-css"><code class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">border-left-width</span><span class="token punctuation">:</span> 3px<span class="token punctuation">;</span> <span class="token comment">/* border-image */</span> <span class="token punctuation">}</span></code></pre> <p>The downside is that these borders do <em>not</em> respect <code>border-radius</code>, so if you need a solution that does, you can use Inspector to peek at how the gradients are added for the cards on the <a href="https://moderncss.dev/">ModernCSS</a> home page 😉</p> </content>
</entry>
<entry>
<title>Pure CSS Shapes 3 Ways</title>
<link href="https://moderncss.dev/pure-css-shapes-3-ways/"/>
<updated>2020-11-15T00:00:00Z</updated>
<id>https://moderncss.dev/pure-css-shapes-3-ways/</id>
<content type="html"><p>Modern CSS - and modern browser support - provides us three excellent methods to create pure, basic CSS shapes. In this tutorial, we will examine how to create CSS triangles using:</p> <ul> <li>borders</li> <li>linear gradients</li> <li><code>clip-path</code></li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="method-1-borders">Method 1: Borders</h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-1-borders" aria-labelledby="method-1-borders"><span hidden="">#</span></a></div> <p>This is the first trick I learned to create CSS triangles, and it's still a solid standby.</p> <p>Given a zero width and zero height element, any values provided <code>border</code> directly intersect and are the only visible indication of an element. This intersection is what we can take advantage of to create a triangle shape.</p> <p>To illustrate how this works, we'll supply a different border color to each side:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid blue<span class="token punctuation">;</span> <span class="token property">border-right-color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span> <span class="token property">border-bottom-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which produces the following, in which you can see we've essentially already achieved 4 triangle shapes:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/87uek6hvhhb8t9anzdq9.png" alt="result of the previously defined CSS rule showing 4 triangles due to the border colors" /></p> <p>In order to create this as a single triangle instead, we first decide which direction we want the triangle pointing. So, if we want it pointing to the right, similar to a &quot;play&quot; icon, we want to keep the left border visible. Then, we set the colors of the other borders to <code>transparent</code>, like so:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px solid transparent<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In the demo image below, I've added a red <code>outline</code> to see the bounding box so we can discuss some improvements.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/16idiqp96hp2uynw4zwd.png" alt="a blue triangle shape pointing to the right with a red outline to show the bounding box" /></p> <p>One improvement we can make is to remove width of the right border to prevent it being included in the total width of the element. We can also set unique values for top and bottom to elongate the triangle visual. Here's a compact way to achieve these results:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token comment">/* top | right | bottom | left */</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 7px 0 7px 10px<span class="token punctuation">;</span> <span class="token property">border-left-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As seen in the updated image below, we first assign a solid, transparent border. Then we define widths such that the top and bottom are a smaller value than the left to adjust the aspect ratio and render an elongated triangle.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0p5n46utvytk2np0uc8h.png" alt="final triangle" /></p> <p>So to point the triangle a different direction, such as up, we just shuffle the values so that the <em>bottom</em> border gains the color value and the <em>top</em> border is set to zero:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token comment">/* top | right | bottom | left */</span> <span class="token property">border-width</span><span class="token punctuation">:</span> 0 7px 10px 7px<span class="token punctuation">;</span> <span class="token property">border-bottom-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Resulting in:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hxiym0rl9k9ygzshnscn.png" alt="demo of the CSS triangle pointing upwards" /></p> <p>Borders are very effective for triangles, but not very extendible beyond that shape without getting more elements involved. This is where our next two methods come to the rescue.</p> <div class="heading-wrapper h2"> <h2 id="method-2-linear-gradient">Method 2: <code>linear-gradient</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-2-linear-gradient" aria-labelledby="method-2-linear-gradient"><span hidden="">#</span></a></div> <p>CSS gradients are created as <code>background-image</code> values.</p> <p>First let's set our stage, if you will, by defining box dimensions and preventing <code>background-repeat</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 8em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 10em<span class="token punctuation">;</span> <span class="token property">background-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span> <span class="token comment">/* Optional - helping us see the bounding box */</span> <span class="token property">outline</span><span class="token punctuation">:</span> 1px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we'll add our first gradient. This will create the appearance of coloring half of our element blue because we are creating a hard-stop at 50% between blue and a transparent value.</p> <pre class="language-css"><code class="language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>45deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Now, if our element was square, this would appear to cut corner to corner, but we ultimately want a slightly different aspect ratio like we did before.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/guscw9fd5wf7xfgkteje.png" alt="progress of adding the first gradient showing a partly blue element but not yet a triangle" /></p> <p>Our goal is to create a triangle with the same appearance as when using our border method. To do this, we will have to adjust the <code>background-size</code> and <code>background-position</code> values.</p> <p>The first adjustment is to change the <code>background-size</code>. In shorthand, the first value is width and the second height. We want our triangle to be allowed 100% of the width, but only 50% of the height, so add the following:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-size</span><span class="token punctuation">:</span> 100% 50%<span class="token punctuation">;</span></code></pre> <p>With our previous <code>linear-gradient</code> unchanged, this is the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/cb3cqq4h57jupkza659i.png" alt="updated triangle resized with background-size showing an odd shape in the upper left of the bounding box" /></p> <p>Due to the <code>45deg</code> angle of the gradient, the shape appears a bit strange. We need to adjust the angle so that the top side of the triangle appears to cut from the top-left corner to the middle of the right side of the bounding box.</p> <p>I'm not a math wizard, so this took a bit of experimentation using DevTools to find the right value 😉</p> <p>Update the <code>linear-gradient</code> value to the following:</p> <pre class="language-css"><code class="language-css"><span class="token function">linear-gradient</span><span class="token punctuation">(</span>32deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span>255<span class="token punctuation">,</span>255<span class="token punctuation">,</span>0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And here's our progress - which, while technically a triangle, isn't quite the full look we want:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/48gy0u5kf6pmp4uofkmw.png" alt="progress of completing one side of the triangle" /></p> <p>While for the border trick we had to rely on the intersection to create the shapes, for <code>linear-gradient</code> we have to take advantage of the ability to add multiple backgrounds to layer the effects and achieve our full triangle.</p> <p>So, we'll duplicate our <code>linear-gradient</code> and update it's degrees value to become a mirror-shape of the first, since it will be positioned below it. This results in the following for the full <code>background-image</code> definition:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>32deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>148deg<span class="token punctuation">,</span> blue 50%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 255<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>But - we still haven't quite completed the effect, as can be seen in the progress image:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/l57qenjmriispnhko0nm.png" alt="the second linear-gradient triangle is overlapping the first" /></p> <p>The reason for the overlap is because the default position of both gradients is <code>0 0</code> - otherwise known as <code>top left</code>. This is fine for our original gradient, but we need to adjust the second.</p> <p>To do this, we need to set multiple values on <code>background-position</code>. These go in the same order as <code>background-image</code>:</p> <pre class="language-css"><code class="language-css"><span class="token property">background-position</span><span class="token punctuation">:</span> top left<span class="token punctuation">,</span> bottom left<span class="token punctuation">;</span></code></pre> <p>And now we have our desired result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ylmk416mow1c48yrj1hb.png" alt="final triangle created with CSS gradients" /></p> <p>The downside of this method is that it's rather inflexible to changing aspect ratio without also re-calculating the degrees.</p> <p>However, CSS gradients can be used to create more shapes especially due to their ability to be layered to create effects.</p> <blockquote> <p>For a master class in CSS gradients for shapes and full illustrations, check out <a href="https://a.singlediv.com/">A Single Div</a> by Lynn Fisher</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="method-3-clip-path">Method 3: <code>clip-path</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#method-3-clip-path" aria-labelledby="method-3-clip-path"><span hidden="">#</span></a></div> <p>This final method is the slimmest and most scalable. It is currently <a href="https://caniuse.com/mdn-css_properties_clip-path_html">slightly lagging in support</a> so be sure to check our own analytics to determine if this is an acceptable solution.</p> <p>Here's our starting point for our element, which is box dimensions and a <code>background-color</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.triangle</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The concept of <code>clip-path</code> is that you use it to draw a polygon shape (or circle, or ellipse) and position it within the element. Any areas outside of the <code>clip-path</code> are effectively not drawn by the browser, thus &quot;clipping&quot; the appearance to just the bounds of the <code>clip-path</code>.</p> <blockquote> <p>To illustrate this more, and to generate your desired <code>clip-path</code> definition, check out the online generator: <a href="https://bennettfeely.com/clippy/">Clippy</a></p> </blockquote> <p>The syntax can be a bit more difficult to get used to, so I definitely suggest using the generator noted above to create your path.</p> <p>For our purposes, here's a triangle pointing to the right:</p> <pre class="language-css"><code class="language-css"><span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>0 0<span class="token punctuation">,</span> 0% 100%<span class="token punctuation">,</span> 100% 50%<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>With a <code>clip-path</code>, you are defining coordinates for every point you place along the path. So in this case, we have a point at the top-left (<code>0 0</code>), bottom-left (<code>0% 100%</code>), and right-center (<code>100% 50%</code>).</p> <p>And here is our result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/we4fhuugu4dp2kvpt9ym.png" alt="completed triangle using clip-path" /></p> <p>While <code>clip-path</code> is very flexible for many shapes, and also the most scalable due to adapting to any bounding box or aspect ratio, there are some caveats.</p> <p>When I mentioned the browser doesn't draw anything outside of the bounding box, this includes borders, <code>box-shadow</code>, and <code>outline</code>. Those things are not re-drawn to fit the clipped shape. This can be a gotcha, and may require additional elements or moving of effects to a parent to replace the lost effects.</p> <blockquote> <p>Here's <a href="https://egghead.io/lessons/css-add-a-cutout-notch-to-an-html-element-with-a-css-polygon-clip-path?af=2s65ms">an egghead video by Colby Fayock</a> to better understand <code>clip-path</code> and how to bring back effects like <code>box-shadow</code></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-shapes-3-ways/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>This demo shows our three ways to create a CSS triangle, which is added to each element using <code>::after</code> and makes use of viewport units to be responsive.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="oNLVvgX" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Custom CSS Styles for Form Inputs and Textareas</title>
<link href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/"/>
<updated>2020-08-31T00:00:00Z</updated>
<id>https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/</id>
<content type="html"><p>We're going to create custom form input and textarea styles that have a near-identical appearance across the top browsers. We'll specifically style the input types of <code>text</code>, <code>date</code>, and <code>file</code>, and style the <code>readonly</code> and <code>disabled</code> states.</p> <p>Read on to learn how to:</p> <ul> <li>reset input styles</li> <li>use <code>hsl</code> for theming of input states</li> <li>ensure all states meet contrast requirements</li> <li>retain a perceivable <code>:focus</code> state for Windows High Contrast mode</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <blockquote> <p>This is the fourth installment in the Modern CSS form field mini-series. Check out episodes 18-20 to learn how to style other common form field types including <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a>, <a href="https://moderncss.dev/pure-css-custom-checkbox-style/">checkboxes</a>, and <a href="https://moderncss.dev/custom-select-styles-with-pure-css/">selects</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-input-styles">Common Issues with Native Input Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#common-issues-with-native-input-styles" aria-labelledby="common-issues-with-native-input-styles"><span hidden="">#</span></a></div> <p>There is a bit more parity between text input styles than we saw with radios, checkboxes, and selects, but inconsistencies nonetheless.</p> <p>Here's a screenshot of the unstyled inputs we're going to address today across (from left) Chrome, Safari, and Firefox.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7scyr2oalmyn8fce7z6x.png" alt="native input fields including text, date, file, and readonly and disabled states in the aforementioned browsers" /></p> <p>We will be looking to unify the initial appearance across browsers and common field types.</p> <blockquote> <p>The <code>date</code> field is unique in that Chrome and Firefox provide formatting and a popup calendar to select from, while Safari offers no comparable functionality. We cannot create this in CSS either, so our goal here is to get as far as we can with creating a similar initial appearance. Check out the <a href="https://caniuse.com/#feat=input-datetime">caniuse for date/time inputs</a>.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="base-html">Base HTML</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#base-html" aria-labelledby="base-html"><span hidden="">#</span></a></div> <p>We're covering a lot of field types, so check the CodePen for the full list. But here is the essential HTML for a text input and a textarea.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>text-input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Text Input<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>input<span class="token punctuation">"</span></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>text-input<span class="token punctuation">"</span></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>text<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>textarea<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Textarea<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>textarea</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>input<span class="token punctuation">"</span></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>textarea<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>textarea</span><span class="token punctuation">></span></span></code></pre> <p>To allow simplifying our styles and preparing to work with the cascade, we've only added one CSS class - <code>input</code> - which is placed directly on the text input and textarea.</p> <p>The label is not part of our styling exercise, but its included as a general requirement, notably with the <code>for</code> attribute having the value of the <code>id</code> on the input.</p> <div class="heading-wrapper h2"> <h2 id="create-css-variables-for-theming">Create CSS Variables for Theming</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#create-css-variables-for-theming" aria-labelledby="create-css-variables-for-theming"><span hidden="">#</span></a></div> <p>For the tutorial, we're going to try a bit different technique for theming by using <code>hsl</code> values.</p> <p>We'll set a grey for the border, and then break down a blue color to be used in our <code>:focus</code> state into its hsl values, including: <code>h</code> for &quot;hue&quot;, <code>s</code> for &quot;saturation&quot;, and <code>l</code> for &quot;lightness&quot;.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--input-border</span><span class="token punctuation">:</span> #8b8a8b<span class="token punctuation">;</span> <span class="token property">--input-focus-h</span><span class="token punctuation">:</span> 245<span class="token punctuation">;</span> <span class="token property">--input-focus-s</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--input-focus-l</span><span class="token punctuation">:</span> 42%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>Each of the tutorials for our form fields has incorporated a bit different method for theming, which can all be extracted and used beyond just forms!</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="accessible-contrast">Accessible Contrast</h3> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#accessible-contrast" aria-labelledby="accessible-contrast"><span hidden="">#</span></a></div> <p>As per all user interface elements, the input border needs to have <em>at least</em> 3:1 contrast against it's surroundings.</p> <p>And, the <code>:focus</code> state needs to have 3:1 contrast against the <em>unfocused</em> state if it involves something like changing the border color <em>or</em>, according to <a href="https://www.w3.org/TR/WCAG22/#focus-appearance-minimum">the WCAG 2.2 draft</a>, a thickness greater than or equal to <code>2px</code>.</p> <p><a href="https://www.w3.org/TR/WCAG22/#focus-appearance-minimum">The draft for WCAG 2.2</a> makes some slight adjustments to <code>:focus</code> requirements, and I encourage you to review them.</p> <div class="heading-wrapper h2"> <h2 id="reset-styles">Reset Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#reset-styles" aria-labelledby="reset-styles"><span hidden="">#</span></a></div> <p>As is included in all my tutorials as a modern best practice, we add the following reset first:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">*, *::before, *::after </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As seen in the initial state of the fields across browsers, some standout differences were in border type, background color, and font properties.</p> <p>Interestingly, <code>font-size</code> and <code>font-family</code> do not inherit from the document like typography elements do, so we need to explicitly set them as part of our reset.</p> <p>Also of note, an input's <code>font-size</code> should compute to at least 16px to avoid zooming being triggered upon interaction in mobile Safari. We can typically assume <code>1rem</code> equals <code>16px</code>, but we'll explicitly set it as a fallback and then use the newer CSS function <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/max"><code>max</code></a> to set <code>16px</code> as the minimum in case it's smaller than <code>1em</code> (h/t to <a href="https://dev.to/danburzo/css-micro-tip-make-mobile-safari-not-have-to-zoom-into-inputs-1fc1">Dan Burzo</a> for this idea).</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">max</span><span class="token punctuation">(</span>16px<span class="token punctuation">,</span> 1em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--input-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We set our <code>border</code> to use the theme variable, and also created a slightly rounded corner.</p> <p>After this update, we're already looking pretty good:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ljnbom4rejrx08mxsa9s.png" alt="updated input styles in Chrome, Safari, and Firefox which all show the inputs with unified grey borders and white backgrounds" /></p> <p>It may be difficult to notice in that screenshot, but another difference is the height of each field. Here's a comparison of the text input to the file input to better see this difference:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/elc4gv33zikdjbbvdekc.png" alt="text input field across browsers compared to file input" /></p> <p>Let's address this with the following which we are applying to our <code>.input</code> class as long as it is not placed on a <code>textarea</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:not(textarea)</span> <span class="token punctuation">{</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 2.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We included <code>line-height: 1</code> since when it's not a <code>textarea</code> it's impossible for an input to be multiline. We also set our height in <code>rem</code> due to considerations of specifically the file input type. If you know you will not be using a file input type, you could use <code>em</code> here instead for flexibility in creating various sized inputs.</p> <p>But, critically, we've lost differentiation between editable and <code>disabled</code> input types. We also want to define <code>readonly</code> with more of a hint that it's also un-editable, but still interactive. And we have a bit more work to do to smooth over the file input type. And, we want to create our themed <code>:focus</code> state.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="file-input-css">File Input CSS</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#file-input-css" aria-labelledby="file-input-css"><span hidden="">#</span></a></div> <p>Let's take another look at just our file input across Chrome, Safari, and Firefox:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mawyssxau7ha2co1dfkb.png" alt="current state of the file input styling across browsers" /></p> <p>We cannot style the button created by the browser, or change the prompt text, but the reset we provided so far did do a bit of work to allow our custom font to be used.</p> <p>We'll make one more adjustment to downsize the font just a bit as when viewed with other field types the inherited button seems quite large, and <code>font-size</code> is our only remaining option to address it. From doing that, we need to adjust the top padding since we set our padding up to be based on <code>em</code>.</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="file"]</span> <span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token property">padding-top</span><span class="token punctuation">:</span> 0.35rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you were expecting a fancier solution, there are plenty of folx who have covered those. My goal here was to provide you a baseline that you can then build from.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="readonly-css-style"><code>readonly</code> CSS Style</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#readonly-css-style" aria-labelledby="readonly-css-style"><span hidden="">#</span></a></div> <p>While not in use often, the <code>readonly</code> attribute prevents additional user input, although the value can be selected, and it is still discoverable by assistive tech.</p> <p>Let's add some styles to enable more of a hint that this field is essentially a placeholder for a previously entered value.</p> <p>To do this, we'll target any <code>.input</code> that also has the <code>[readonly]</code> attriute. Attribute selectors are a very handy method with wide application, and definitely worth adding to (or updating your awareness of) in your CSS toolbox.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input[readonly]</span> <span class="token punctuation">{</span> <span class="token property">border-style</span><span class="token punctuation">:</span> dotted<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #777<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In addition to swapping for a <code>dotted</code> border, we've also assigned it the <code>not-allowed</code> cursor and enforced a medium-grey text color.</p> <p>As seen in the following gif, the user cannot interact with the field except to highlight/copy the value.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/iouhzeibpjqshlwt66mf.gif" alt="the user mouses over the readonly field and is show the not-allowed cursor, and then double-clicks to highlight the value" /></p> <div class="heading-wrapper h2"> <h2 id="disabled-input-and-textarea-style">Disabled Input and Textarea Style</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#disabled-input-and-textarea-style" aria-labelledby="disabled-input-and-textarea-style"><span hidden="">#</span></a></div> <p>Similar to <code>readonly</code>, we'll use an attribute selector to update the style for disabled fields. We are attaching it to the <code>.input</code> class so it applies on textareas as well as our other input types.</p> <p>We'll make use of our CSS variable to update the border color to a muted grey, and the field background to a very light grey. We'll also again apply the <code>not-allowed</code> cursor as just an extra hint that the field is not interactive.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input[disabled]</span> <span class="token punctuation">{</span> <span class="token property">--input-border</span><span class="token punctuation">:</span> #ccc<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here is the result for both a text input and a textarea:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/laaw2w94zl43c187ojae.png" alt="Alt Text" /></p> <blockquote> <p><strong>Accessibility Note</strong>: <code>disabled</code> fields are not necessarily discoverable by assistive tech since they are not focusable. They also are not required to meet even the typical 3:1 contrast threshold for user interface elements, but we've kept with user expectations by setting them to shades of grey.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="textarea-styles">Textarea Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#textarea-styles" aria-labelledby="textarea-styles"><span hidden="">#</span></a></div> <p>Our <code>textarea</code> is really close, but there's one property I want to mention since it's unique to the inherent behavior of textareas.</p> <p>That property is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/resize"><code>resize</code></a>, which allows you to specify which direction the <code>textarea</code> can be resized, or if it even can at all.</p> <p>While you definitely should allow the <code>textarea</code> to retain the resize function under general circumstances, you can limit it to just vertical resizing to prevent layout breakage from a user dragging it really wide, for example.</p> <p>We'll apply this property by scoping our <code>.input</code> class to when it's applied on a <code>textarea</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">textarea.input</span> <span class="token punctuation">{</span> <span class="token property">resize</span><span class="token punctuation">:</span> vertical<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Try it out in the final CodePen demo!</p> <div class="heading-wrapper h2"> <h2 id="focus-state-styles"><code>:focus</code> State Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#focus-state-styles" aria-labelledby="focus-state-styles"><span hidden="">#</span></a></div> <p>Ok, we've completed the initial styles for our inputs and the textarea, but we need to handle for a very important state: <code>:focus</code>.</p> <p>We're going to go for a combo effect that changes the border color to a value that meets 3:1 contrast against the unfocused state, but also adds a <code>box-shadow</code> for a bit of extra highlighting.</p> <p>And here's why we defined our theme color of the focus state in hsl: it means we can create a variant of the border color by updating just the lightness value.</p> <p>First, we define the border color by constructing the full hsl value from the individual CSS variable values:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token property">border-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-h<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-s<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-l<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we add in the <code>box-shadow</code> which will only use blur to create essentially a double-border effect. <code>calc()</code> is acceptable to use inside <code>hsla</code>, so we use it to lighten the original value by 40%, and also allow just a bit of alpha transparency:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 3px <span class="token function">hsla</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-h<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--input-focus-s<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--input-focus-l<span class="token punctuation">)</span> + 40%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Note that we've now added a new context for our contrast, which is the <code>:focus</code> border vs. the <code>:focus</code> <code>box-shadow</code>, so ensure the computed difference for your chosen colors is at least 3:1 if using this method.</p> <p>Optionally, jump back up to the <code>.input</code> rule and add a <code>transition</code> to animate the <code>box-shadow</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">transition</span><span class="token punctuation">:</span> 180ms box-shadow ease-in-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Finally, we don't want to forget Windows High Contrast mode which will not see the <code>box-shadow</code> or be able to detect the border color change. So, we include a transparent outline for those users:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.input:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</span><span class="token punctuation">:</span> 3px solid transparent<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>We also use this technique in the episode covering <a href="https://moderncss.dev/css-button-styling-guide/">button styles</a>.</p> </blockquote> <p>Here's a gif demo of focusing into the text input:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jqawfxgvv2lb2x6f0q3w.gif" alt="keyboard focusing into and out of the text input" /></p> <p>And here's the appearance for the <code>readonly</code> field, since it has a different <code>border-style</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/lg358oqeytrd8kdnokwo.png" alt="the readonly field when focused" /></p> <p>In the CodePen HTML, there is a comment with an example of using an inline style to define an updated visual such as for an error state. Again, keep in mind that we are <em>lightening</em> the provided <code>--input-focus-l</code> value by 40%, <em>and</em> the focused border color must be at least 3:1 contrast against the unfocused color, so consider that when you alter the CSS variable values.</p> <div class="heading-wrapper h2"> <h2 id="input-mode-and-autocomplete">Input Mode and Autocomplete</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#input-mode-and-autocomplete" aria-labelledby="input-mode-and-autocomplete"><span hidden="">#</span></a></div> <p>There are two aditional attributes that can help improve the user experience, particularly on mobile, in addition to using the correct input type (ex: email).</p> <p>The first is defining the <code>inputmode</code>, which provides an altered keyboard or keypad that better matches the expected data. <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode">Read up on available <code>inputmode</code> values on MDN &gt;</a></p> <p>Second is <code>autocomplete</code> which has far more options than <code>on</code> or <code>off</code>. For example, I always appreciate that on iPhone when Google sends me a confirmation code by text the keyboard &quot;just knows&quot; what that value is. Turns out, that's thanks to <code>autocomplete=&quot;one-time-code&quot;</code>!</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete">Check out the full list of <code>autocomplete</code> values</a> that allow you to hint at the value expected and really boost the user experience of your forms for users that make use of auto-filling values.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/custom-css-styles-for-form-inputs-and-textareas/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>First, here's a final look at our solution across (from left) Chrome, Safari, and Firefox. The file input still sticks out a bit when viewed side by side, but in the flow of a form on an individual browser it's definitely acceptable.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/c05aw0mbb7ndptdn794z.png" alt="final input and textarea styles across the aforementioned browsers" /></p> <p>Here is the solution with all the field types we covered represented.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="KKzqEzz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Custom Select Styles with Pure CSS</title>
<link href="https://moderncss.dev/custom-select-styles-with-pure-css/"/>
<updated>2020-08-15T00:00:00Z</updated>
<id>https://moderncss.dev/custom-select-styles-with-pure-css/</id>
<content type="html"><p>Modern CSS gives us a range of properties to achieve custom select styles that have a near-identical initial appearance for single, multiple, and disabled <code>select</code> elements across the top browsers.</p> <p>A few properties and techniques our solution will use:</p> <ul> <li><code>clip-path</code> to create the custom dropdown arrow</li> <li>CSS grid layout to align the native select and arrow</li> <li>custom CSS variables for flexible styling</li> <li><code>em</code> units for relative sizing</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-selects">Common Issues with Native Selects</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#common-issues-with-native-selects" aria-labelledby="common-issues-with-native-selects"><span hidden="">#</span></a></div> <p>As with all form field types, <code>&lt;select&gt;</code> varies across browsers in its initial appearance.</p> <p>From left to right, here is the initial appearance for <code>&lt;select&gt;</code> in Firefox, Chrome, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/s8g71cd7l6rywzrx3js8.png" alt="initial native select appearance with no custom styling" /></p> <p>The differences include box size, font-size, line-height, and most standout is the difference in how the dropdown indicator is styled.</p> <p>Our goal is to create the same initial appearance across these browsers, inclusive of multiple selects, and disabled states.</p> <blockquote> <p>Note: The dropdown list is still not stylable, so once the <code>&lt;select&gt;</code> is opened, it will still pick up the individual browser's styles for the <code>option</code> list. This is ok - we can deal with that to retain the free accessibility of a native select!</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="base-html">Base HTML</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#base-html" aria-labelledby="base-html"><span hidden="">#</span></a></div> <p>We'll focus on a single <code>&lt;select&gt;</code> to begin.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>standard-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Standard Select<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <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>select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</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>standard-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 4<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 5<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option length<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Option that has too long of a value to fit <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>The label is not part of our styling exercise, but its included as a general requirement, notably with the <code>for</code> attribute having the value of the <code>id</code> on the <code>&lt;select&gt;</code>.</p> <p>To accomplish our custom styles, we've wrapped the native select in an extra div with class of <code>select</code> for simplicity in this tutorial.</p> <div class="heading-wrapper h2"> <h2 id="reset-and-remove-inherited-styles">Reset and Remove Inherited Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#reset-and-remove-inherited-styles" aria-labelledby="reset-and-remove-inherited-styles"><span hidden="">#</span></a></div> <p>As is included in all my tutorials as a modern best practice, we add the following reset first:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">*, *::before, *::after </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Following that, we can begin the rule for the native <code>select</code> and apply the following to rest its appearance:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select </span><span class="token punctuation">{</span> <span class="token comment">// A reset of styles, including removing the default dropdown arrow</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">// Additional resets for further consistency</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 1em 0 0<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>While most of those are likely familiar, the oddball out is <code>appearance</code>. This is an infrequently used property and you'll note that it is not quite where we'd like it for <a href="https://caniuse.com/#search=appearance">support</a>, but what it's primarily providing for us in this instance is the removal of the native browser dropdown arrow.</p> <blockquote> <p>Note: The CodePen is set up to use <a href="https://autoprefixer.github.io/">autoprefixer</a> which will add required pre-fixed versions of the <code>appearance</code> property. You may need to specifically set this up for your project, or manually add them. My <a href="https://5t3ph.github.io/html-sass-jumpstart/">HTML / Sass Jumpstart</a> includes autoprefixer as part of the production build.</p> </blockquote> <p>The good news is, we can add one more rule to gain removal of the arrow for lower IE versions if you need it:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select::-ms-expand </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>This tip found in the excellent article from Filament Group that shows <a href="https://www.filamentgroup.com/lab/select-css.html">an alternate method to create select styles</a></em>.</p> <p>The last part is to remove the default <code>outline</code>. Don't worry - we'll add a replacement later on for the <code>:focus</code> state!</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">outline</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></code></pre> <p>And here's a gif of our progress. You can see there is now zero visual indication that this is a <code>select</code> prior to clicking on it:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d8i63lg16q68v8eyt5ef.gif" alt="demo of interacting with the reset select" /></p> <div class="heading-wrapper h2"> <h2 id="custom-select-box-styles">Custom Select Box Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#custom-select-box-styles" aria-labelledby="custom-select-box-styles"><span hidden="">#</span></a></div> <p>First, let's set up some CSS variables. This will allow our select to be flexibly re-colored such as to represent an error state.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--select-border</span><span class="token punctuation">:</span> #777<span class="token punctuation">;</span> <span class="token property">--select-focus</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">--select-arrow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--select-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><strong>Accessibility note</strong>: As a user interface element, the select border must have a 3:1 contrast or greater against the surrounding surface color.</p> </blockquote> <p>Now it's time to create the custom select styles which we will apply to the our wrapping <code>div.select</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 15ch<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--select-border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to top<span class="token punctuation">,</span> #f9f9f9<span class="token punctuation">,</span> #fff 33%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>First, we set up some width constraints. The <code>min-width</code> and <code>max-width</code> values are mostly for this demo, and you may choose to drop or alter it for your use case.</p> <p>Then we apply some box model properties, including <code>border</code>, <code>border-radius</code>, and <code>padding</code>. Note the use of the <code>em</code> unit which will keep these properties proportional to the set <code>font-size</code>.</p> <p>In the reset styles, we set several properties to <code>inherit</code>, so here we define those, including <code>font-size</code>, <code>cursor</code>, and <code>line-height</code>.</p> <p>Finally, we supply it background properties, including a gradient for the slightest bit of dimension. If you remove the background properties, the select will be transparent and pick up the page background. This may be desirable, however, be aware and test the effects on contrast.</p> <p>And here's our progress: <img src="https://dev-to-uploads.s3.amazonaws.com/i/prn99ajlym5ehflhqia9.png" alt="updated select now has a visually apparent box appearance" /></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="custom-select-dropdown-arrow">Custom Select Dropdown Arrow</h3> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#custom-select-dropdown-arrow" aria-labelledby="custom-select-dropdown-arrow"><span hidden="">#</span></a></div> <p>For our dropdown arrow, we are going to use one of the most exciting modern CSS properties: <code>clip-path</code>.</p> <p>Clip paths let us make all kind of shapes by &quot;clipping&quot; the otherwise square and rectangle shapes we receive as defaults from most elements. I had fun using <code>clip-path</code> on <a href="https://thinkdobecreate.com/">my recent portfolio site redesign</a>.</p> <p>Prior to <code>clip-path</code> having better support, alternative methods included:</p> <ul> <li><code>background-image</code> - typically a png, slightly more modern would be an SVG</li> <li>an inline SVG as an additional element</li> <li>the <a href="https://css-tricks.com/the-shapes-of-css/#triangle-down-shape">border trick</a> to create a triangle</li> </ul> <p>SVG may feel like the optimal solution, however when used as a <code>background-image</code> it loses the ability to act like an icon in the sense of not being able to alter its properties such as fill color without redefining it entirely. This means we cannot use our CSS custom variable.</p> <p>Placing an SVG inline solves the <code>fill</code> color issue, however it means including one more element every time a <code>&lt;select&gt;</code> is defined.</p> <p>With <code>clip-path</code>, we get a crisp, scalable arrow &quot;graphic&quot; that <em>feels like</em> an SVG but with the benefits of being able to use our custom variable and being contained in the style vs. the HTML markup.</p> <p>To create the arrow, we will define it as an <code>::after</code> pseudo-element.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select::after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.8em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--select-arrow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>100% 0%<span class="token punctuation">,</span> 0 0%<span class="token punctuation">,</span> 50% 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>clip-path</code> syntax is a little strange, and since it's not really the focus of this article, I recommend the following resources:</p> <ul> <li>Colby Fayock explans the syntax with an example in <a href="https://egghead.io/lessons/css-add-a-cutout-notch-to-an-html-element-with-a-css-polygon-clip-path">this egghead video</a></li> <li><a href="https://bennettfeely.com/clippy/">Clippy</a> is an online tool that allows you to select a shape and adjust the points while dynamically generating the <code>clip-path</code> CSS</li> </ul> <p>If you're following along, you may have noticed the arrow is not appearing despite defining <code>width</code> and <code>height</code>. When inspected, its found that the <code>::after</code> is not actually being allowed it's width.</p> <p>We will resolve this by updating our <code>.select</code> to use CSS grid layout.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This lets the arrow appear by essentially extending it a display value akin to &quot;block&quot;.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ztphtxex4qzjywvowoiv.png" alt="clip-path arrow now appears below the native select" /></p> <p>At this stage we can verify that we have indeed created a triangle.</p> <p>To fix the alignment, we'll use my favorite CSS grid hack (old hat to you if you've read a few articles around here!).</p> <p>Old CSS solution: <code>position: absolute</code> New CSS solution: A single <code>grid-template-areas</code> to contain them all</p> <p>First we'll define our area, then define that the <code>select</code> and the <code>::after</code> both use it. The name is scoped to the element its created for, and we'll keep it easy by calling it &quot;select&quot;:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"select"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">select, .select:after </span><span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> select<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Which gives us an overlap of the arrow above the native select due to stacking context via source order:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/esdzj5jsvmddxefyy9jn.png" alt="preview of the updated arrow position above the native select" /></p> <p>We can now use grid properties to finalize the alignment of each element:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.select:after </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">justify-self</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Ta-da!</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/na2vayzyanyrfx2gui9c.png" alt="finished initial styles for the custom select" /></p> <div class="heading-wrapper h3"> <h3 id="focus-state"><code>:focus</code> State</h3> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#focus-state" aria-labelledby="focus-state"><span hidden="">#</span></a></div> <p>Oh yeah - remember how we removed the <code>outline</code>? We need to resolve the missing <code>:focus</code> state from dropping that.</p> <p>There is an upcoming property we could use called <code>:focus-within</code> but it's still best to include a polyfill for it at this time.</p> <p>For this tutorial, we'll use an alternate method that achieves the same result, just a bit heftier.</p> <p>Unfortunately, this means we need to add one more element into the DOM.</p> <p>After the native select element, as the last child within <code>.select</code>, add:</p> <pre class="language-html"><code class="language-html"><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>focus<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span></code></pre> <p>Why after? Because since this is a pure CSS solution, placing it after the native select means we can alter it when the <code>select</code> is focused by use of the adjacent sibling selector - <code>+</code>.</p> <p>This allows us to create the following rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select:focus + .focus </span><span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">bottom</span><span class="token punctuation">:</span> -1px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--select-focus<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>You may be wondering why we're back to <code>position: absolute</code> after just learning the previous <code>grid-area</code> hack.</p> <p>The reason is to avoid recalculating adjustments based on padding. If you try it on your own, you'll see that even setting <code>width</code> and <code>height</code> to 100% still makes it sit within the padding.</p> <p>The job <code>position: absolute</code> does best is matching the size of an element. We're pulling it an extra pixel in each direction to make sure it overlaps the border property.</p> <p>But, we need to make one more addition to <code>.select</code> to ensure that it's relative to our select by - well, <code>position: relative</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span></code></pre> <p>And here's our custom select all together as seen in Chrome:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/cykucssuq6909qwrgnk1.gif" alt="gif demo of focusing and selecting an option in the custom select" /></p> <div class="heading-wrapper h2"> <h2 id="multiple-select">Multiple Select</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#multiple-select" aria-labelledby="multiple-select"><span hidden="">#</span></a></div> <p>Selects come in a second flavor, which allows a user to select more than one option. From the HTML perspective, this simply means add the <code>multiple</code> attribute, but we'll also add a class to help create style adjustments called <code>select--multiple</code>:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>multi-select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Multiple Select<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <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>select select--multiple<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</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>multi-select<span class="token punctuation">"</span></span> <span class="token attr-name">multiple</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 2<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 3<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 4<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option 5<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Option 5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Option length<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Option that has too long of a value to fit <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">></span></span> <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>focus<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>And looking at it, we can see it's inherited most of our styles favorably, except we don't need the arrow in this view.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/gs313hkhignewqzgd661.png" alt="multiple select with inherited styles as previously defined" /></p> <p>This is a quick fix to adjust our selector that defines the arrow. We use <code>:not()</code> to exclude our newly defined class:</p> <pre class="language-scss"><code class="language-scss">.<span class="token property">select</span><span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>.select--multiple<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token punctuation">:</span>after</code></pre> <p>We have a couple of minor adjustments to make for the multiple select, the first is removing padding that was previously added to make room for the arrow:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] </span><span class="token punctuation">{</span> <span class="token property">padding-right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>By default, options with a long value will overflow visible area and be clipped, but I found that the main browsers allow the wrapping to be overridden if you desire:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] option </span><span class="token punctuation">{</span> <span class="token property">white-space</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Optionally, we can set a <code>height</code> on the select to bring a bit more reliable cross-browser behavior. Through testing this, I learned that Chrome and Firefox will show a partial option, but Safari will completely hide an option that is not able to be fully in view.</p> <p>The height must be set directly on the native select. Given our other styles, the value <code>6rem</code> will be able to show 3 options:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">select[multiple] </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 6rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>At this point, due to current browser support, we have made as much adjustments as we are able.</p> <blockquote> <p>The <code>:selected</code> state of the <code>options</code> is fairly customizable in Chrome, somewhat in Firefox, and not at all in Safari. See the <a href="https://moderncss.dev/custom-select-styles-with-pure-css/#demo">CodePen demo</a> for a section that can be uncommented to preview this.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="disabled-styles"><code>:disabled</code> Styles</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#disabled-styles" aria-labelledby="disabled-styles"><span hidden="">#</span></a></div> <p>While I would advocate for simply not showing disabled controls, we should prepare the styles for that state just to cover our bases.</p> <p>To emphasis the disabled state, we want to apply a greyed background. But since we've set background styles on <code>.select</code> and there isn't a <code>:parent</code> selector, we need to create one last class to handle for this state:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.select--disabled </span><span class="token punctuation">{</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to top<span class="token punctuation">,</span> #ddd<span class="token punctuation">,</span> #eee 33%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here we've updated the cursor as an extra hint that the field cannot be interacted with, and updated the background values we previously set to be white to now be more grey for the disabled state.</p> <p>This results in the following appearances:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/iwmnikta0b915i1a0d11.png" alt="previous of the disabled state styles for both single and multiple select" /></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/custom-select-styles-with-pure-css/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>You can test it for yourself, but here's a preview of the full solution across (from left) the Firefox, Chrome, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ct4v1q5jbxznf02zjiy9.png" alt="final styled selects across the browsers mentioned" /></p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="MWyyYNz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Pure CSS Custom Checkbox Style</title>
<link href="https://moderncss.dev/pure-css-custom-checkbox-style/"/>
<updated>2020-07-27T00:00:00Z</updated>
<id>https://moderncss.dev/pure-css-custom-checkbox-style/</id>
<content type="html"><p>We'll create custom, cross-browser, theme-able, scalable checkboxes in pure CSS with the following:</p> <ul> <li><code>currentColor</code> and CSS custom properties for theme-ability</li> <li><code>em</code> units for relative sizing</li> <li>use of pseudo elements for the <code>:checked</code> indicator</li> <li>CSS grid layout to align the input and label</li> </ul> <p>Many of the concepts here overlap with our <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">custom styled radio buttons</a> from episode 18, with the addition of styling for the <code>:disabled</code> state</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="checkbox-html">Checkbox HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#checkbox-html" aria-labelledby="checkbox-html"><span hidden="">#</span></a></div> <p>In the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio buttons</a> article, we explored the two valid ways to markup input fields. Much like then, we will select the method where the label wraps the input.</p> <p>Here's our base HTML for testing both an unchecked and checked state:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Checkbox <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkbox-checked<span class="token punctuation">"</span></span> <span class="token attr-name">checked</span> <span class="token punctuation">/></span></span> Checkbox - checked <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-checkboxes">Common Issues With Native Checkboxes</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#common-issues-with-native-checkboxes" aria-labelledby="common-issues-with-native-checkboxes"><span hidden="">#</span></a></div> <p>As with the radio buttons, the checkbox appearance varies across browsers.</p> <p>Here's the base styles across (in order from left) Chrome, Firefox, and Safari:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hd8limqlb7v3wqf197e3.png" alt="default checkboxes in Chrome, Firefox, and Safari" /></p> <p>Also like with radio buttons, the checkbox doesn't scale along with the <code>font-size</code>.</p> <p>Our solution will accomplish the following goals:</p> <ul> <li>scale with the <code>font-size</code> provided to the label</li> <li>gain the same color as provided to the label for ease of theme-ability</li> <li>achieve a consistent, cross-browser design style, including <code>:focus</code> state</li> <li>maintain keyboard and color contrast accessibility</li> </ul> <blockquote> <p>Our styles will begin with the same variable and reset as used for the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#theme-variable-and-box-sizing-reset">radio buttons</a></p> </blockquote> <div class="heading-wrapper h2"> <h2 id="label-styles">Label Styles</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#label-styles" aria-labelledby="label-styles"><span hidden="">#</span></a></div> <p>Our label uses the class of <code>.form-control</code>. The base styles we'll include here are font styles. Recall from earlier that the <code>font-size</code> will not yet have an effect on the visual size of the checkbox <code>input</code>.</p> <details open=""> <summary>CSS for ".form-control font styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control</span> <span class="token punctuation">{</span> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-632 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-632"> <input type="checkbox" name="checkbox" /> Checkbox </label> <label class="form-control-632"> <input type="checkbox" name="checkbox-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>We're using an abnormally large <code>font-size</code> just to emphasize the visual changes for purposes of the tutorial demo.</p> <p>Our label is also the layout container for our design, and we're going to set it up to use CSS grid layout to take advantage of <code>gap</code>.</p> <details open=""> <summary>CSS for ".form-control grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 1em auto<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-122 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-122"> <input type="checkbox" name="checkbox-layout" /> Checkbox </label> <label class="form-control-122"> <input type="checkbox" name="checkbox-layout-checked" checked="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="custom-checkbox-style">Custom Checkbox Style</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#custom-checkbox-style" aria-labelledby="custom-checkbox-style"><span hidden="">#</span></a></div> <p>Alright, now we'll get into restyling the checkbox to be our custom control.</p> <blockquote> <p>The original version of this tutorial demonstrated use of extra elements to achieve the desired effect. Thanks to improved support of <code>appearance: none</code> and with appreciation to <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">Scott O'Hara's post on styling radio buttons and checkboxes</a>, we can rely on pseudo elements instead!</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="step-1-hide-the-native-checkbox-input">Step 1: Hide the Native Checkbox Input</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-1-hide-the-native-checkbox-input" aria-labelledby="step-1-hide-the-native-checkbox-input"><span hidden="">#</span></a></div> <p>We need to reset the native checkbox input styles, but keep it interactive to enable proper keyboard interaction and also to maintain access to the <code>:focus</code> state.</p> <p>To accomplish this, we only need to set <code>appearance: none</code>. This removes nearly all inherited browser styles <em>and</em> <strong>gives us access to styling the input's pseudo elements</strong>. Notice we have two additional properties to complete the reset.</p> <details open=""> <summary>CSS for "hiding the native checkbox input"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* Add if not using autoprefixer */</span> <span class="token property">-webkit-appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* For iOS &lt; 15 to remove gradient background */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token comment">/* Not removed via appearance */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-990 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-990 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-990"> <input type="checkbox" name="checkbox-hiding" /> Checkbox </label> <label class="form-control-990"> <input type="checkbox" name="checkbox-hiding-checked" checked="" /> Checkbox - checked </label> </div> </div> <blockquote> <p><strong>Worried about support</strong>? This combination of using <code>appearance: none</code> and the ability to style the input's pseudo elements has been supported since 2017 in Chrome, Safari, and Firefox, and in Edge since their switch to Chromium in May 2020.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="step-2-custom-unchecked-checkbox-styles">Step 2: Custom Unchecked Checkbox Styles</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-2-custom-unchecked-checkbox-styles" aria-labelledby="step-2-custom-unchecked-checkbox-styles"><span hidden="">#</span></a></div> <p>For our custom checkbox, we'll update box styles on the base input element. This includes inheriting the font styles to ensure the use of <code>em</code> produces the desired sizing outcome, as well as using <code>currentColor</code> to inherit any update on the label's color.</p> <p>We use <code>em</code> for the <code>width</code>, <code>height</code>, and <code>border-width</code> value to maintain the relative appearance. We're also customizing the <code>border-radius</code> with another <code>em</code> relative style.</p> <details open=""> <summary>CSS for "custom unchecked checkbox styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>-0.075em<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token selector">.form-control + .form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-394 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-394 + .form-control-394 { margin-top: 1em; } .form-control-394 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-394"> <input type="checkbox" name="checkbox-unchecked" /> Checkbox </label> <label class="form-control-394"> <input type="checkbox" name="checkbox-unchecked-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>Our style updates includes a rule to give some space between our checkboxes by applying <code>margin-top</code> with the help of the <a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">adjacent sibling combinator</a>.</p> <p>Finally, as discussed in our <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio button tutorial</a>, we do a small adjustment on the label vs. checkbox alignment using a <code>transform</code> to nudge it up half the width of the border.</p> <div class="heading-wrapper h3"> <h3 id="step-3-styling-checked-vs-unchecked-state">Step 3: Styling <code>:checked</code> vs Unchecked State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-3-styling-checked-vs-unchecked-state" aria-labelledby="step-3-styling-checked-vs-unchecked-state"><span hidden="">#</span></a></div> <p>To prepare for the incoming pseudo element, we first need to change the display behavior of the input to use grid.</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is the quickest way to align the <code>:before</code> to the horizontal and vertical center of our custom control.</p> <p>It's now time to bring in our <code>::before</code> pseudo element which will be styled in order to represent the <code>:checked</code> state. We create the <code>:before</code> element, including a transition and using transform hide it with <code>scale(0)</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>box-shadow</code> instead of <code>background-color</code> will enable the state of the radio to be visible when printed (h/t <a href="https://dev.to/alvaromontoro/comment/1214h">Alvaro Montoro</a>).</p> <p>Finally, when the <code>input</code> is <code>:checked</code>, we make it visible with <code>scale(1)</code> with a nicely animated result thanks to the <code>transition</code>. Be sure to change the checkbox state to see the animation!</p> <details open=""> <summary>CSS for ":checked state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]:checked::before</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-728 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-728 + .form-control-728 { margin-top: 1em; } .form-control-728 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-728 input[type="checkbox"]::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); } .form-control-728 input[type="checkbox"]:checked::before { transform: scale(1) !important; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-728"> <input type="checkbox" name="checkbox-checked-state" /> Checkbox </label> <label class="form-control-728"> <input type="checkbox" name="checkbox-checked-state-checked" checked="" /> Checkbox - checked </label> </div> </div> <h4>High Contrast Themes and Forced Colors</h4> <p>As reviewed in the radio buttons tutorial, one more state we need to ensure our radio responds to is what you may hear referred to as <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">&quot;Windows High Contrast Mode&quot; (WHCM)</a>. In this mode, the user's operating system swaps out color-related properties for a reduced palette which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors">an incoming part of the CSS spec called &quot;forced-colors&quot;</a>.</p> <p>Since <code>box-shadow</code> is removed, we'll ensure the <code>:checked</code> state is visible by providing a <code>background-color</code>, which is normally removed in forced-colors mode, but will be retained if we use one of the defined forced colors. In this case, we're selecting <code>CanvasText</code> which will match the regular body text color.</p> <p>Due to the style stacking order, our <code>box-shadow</code> that we've themed for use in regular mode is actually visuallly placed <em>over</em> the <code>background-color</code>, meaning we can use both without any further modifications.</p> <details open=""> <summary>CSS for "supporting forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token comment">/* Windows High Contrast Mode */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-259 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-259 + .form-control-259 { margin-top: 1em; } .form-control-259 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-259 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-259 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-259"> <input type="checkbox" name="checkbox-forced-colors" /> Checkbox </label> <label class="form-control-259"> <input type="checkbox" name="checkbox-forced-colors-checked" checked="" /> Checkbox - checked </label> </div> </div> <h4>Creating the &quot;Checkmark&quot; Shape</h4> <p>Right now, the filled-in state is OK, but it would be ideal to have it shaped as a checkmark to match the more expected pattern.</p> <p>We have a few options here, such as bringing in an SVG as a background image. However, that solution means losing access to CSS custom properties which we are relying on to &quot;theme&quot; our inputs.</p> <p>Instead, we'll re-shape the default box by using the <code>clip-path</code> property. This property allows us to treat the pseudo element's box similar to a vector element being reshaped with the pen tool. We define coordinates to redraw the shape between. You can use <a href="https://bennettfeely.com/clippy/">this handy clip-path generator</a> to draw your own shapes or instantly pick up common ones. We also use <code>clip-path</code> to create a <a href="https://moderncss.dev/custom-select-styles-with-pure-css/">custom select dropdown arrow</a>.</p> <p>As a matter of preference, I also alter the <code>transform-origin</code> to use a value of <code>bottom left</code> instead of the default of <code>center</code> to mimic a sort of &quot;checking in&quot; animation.</p> <details open=""> <summary>CSS for "creating a checkmark with clip-path"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> bottom left<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>14% 44%<span class="token punctuation">,</span> 0 65%<span class="token punctuation">,</span> 50% 100%<span class="token punctuation">,</span> 100% 16%<span class="token punctuation">,</span> 80% 0%<span class="token punctuation">,</span> 43% 62%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-698 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-698 + .form-control-698 { margin-top: 1em; } .form-control-698 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-698 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-698 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-698"> <input type="checkbox" name="checkbox-checkmark" /> Checkbox </label> <label class="form-control-698"> <input type="checkbox" name="checkbox-checkmark-checked" checked="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h3"> <h3 id="step-4-the-focus-state">Step 4: The <code>:focus</code> state</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#step-4-the-focus-state" aria-labelledby="step-4-the-focus-state"><span hidden="">#</span></a></div> <p>In the earlier version of this tutorial, we used <code>box-shadow</code>, but now we have two improved features for the humble <code>outline</code>. First, we can use <code>outline-offset</code> to create a bit of space between the input and the outline. Second, evergreen browsers now support <code>outline</code> following <code>border-radius</code>!</p> <blockquote> <p>Remember: <code>:focus</code> is a temporary state, but it's very important that it is highly visible to ensure the accessibility of your form controls and other interactive elements.</p> </blockquote> <details open=""> <summary>CSS for ":focus state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="checkbox"]:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</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> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</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> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-895 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-895 + .form-control-895 { margin-top: 1em; } .form-control-895 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-895 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-895 input:checked::before { transform: scale(1); } .form-control-895 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-895"> <input type="checkbox" name="checkbox-focus" /> Checkbox </label> <label class="form-control-895"> <input type="checkbox" name="checkbox-focus-checked" checked="" /> Checkbox - checked </label> </div> </div> <p>This concludes our critical styles for the checkbox. If you're interested in an additional method to style the label, check out the <a href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/">radio button tutorial</a> to learn how to use <code>:focus-within</code>.</p> <div class="heading-wrapper h3"> <h3 id="styles-for-disabled-checkboxes">Styles For <code>:disabled</code> Checkboxes</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#styles-for-disabled-checkboxes" aria-labelledby="styles-for-disabled-checkboxes"><span hidden="">#</span></a></div> <p>One step not present in the radio buttons tutorial was styling for the <code>:disabled</code> state.</p> <p>This will follow a similar pattern as for our previous states, with the change here mostly being to update the color to a grey. We first re-assign the main <code>--form-control-color</code> to the new <code>--form-control-disabled</code> variable. Then, set the <code>color</code> property to use the disabled color.</p> <details open=""> <summary>CSS for ":disabled state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--form-control-disabled</span><span class="token punctuation">:</span> #959495<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="checkbox"]:disabled</span> <span class="token punctuation">{</span> <span class="token property">--form-control-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-539 { --form-control-color: rebeccapurple; --form-control-disabled: #959495; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-539 + .form-control-539 { margin-top: 1em; } .form-control-539 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-539 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-539 input:checked::before { transform: scale(1); } .form-control-539 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } .form-control-539 input:disabled { --form-control-color: var(--form-control-disabled); color: var(--form-control-disabled); cursor: not-allowed; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-539"> <input type="checkbox" name="checkbox-disabled" disabled="" /> Checkbox </label> <label class="form-control-539"> <input type="checkbox" name="checkbox-disabled-checked" checked="" disabled="" /> Checkbox - checked </label> </div> </div> <p>We've also updated to set the cursor to <code>not-allowed</code> as an additional visual cue that these inputs are not presently interactive.</p> <p>But we've hit a snag. Since the label is the parent element, we don't currently have a way in CSS alone to style it based on the <code>:disabled</code> state.</p> <p>For a CSS-only solution, we need to create an add an extra class to the label when it is known that the checkbox is disabled. Since this state can't be changed by the user, this will generally be an acceptable additional step.</p> <details open=""> <summary>CSS for ":disabled state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control--disabled</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-disabled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-398 { --form-control-color: rebeccapurple; --form-control-disabled: #959495; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-398 + .form-control-398 { margin-top: 1em; } .form-control-398 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 0.15em; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-398 input::before { content: ""; width: 0.65em; height: 0.65em; transform: scale(0); transform-origin: bottom left; transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); } .form-control-398 input:checked::before { transform: scale(1); } .form-control-398 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } .form-control-398 input:disabled { --form-control-color: var(--form-control-disabled); color: var(--form-control-disabled); cursor: not-allowed; } .form-control--disabled-398 { color: var(--form-control-disabled); cursor: not-allowed; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-398 form-control--disabled-398"> <input type="checkbox" name="checkbox-disabled-label" disabled="" /> Checkbox </label> <label class="form-control-398 form-control--disabled-398"> <input type="checkbox" name="checkbox-disabled-label-checked" checked="" disabled="" /> Checkbox - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-checkbox-style/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Here's a demo that includes the <code>:disabled</code> styles, and also shows how the power of CSS variables + the use of <code>currentColor</code> means we can re-theme an individual checkbox with a simple inline style. This is very useful for things like a quick change to an error state.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="RwrOygP" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Pure CSS Custom Styled Radio Buttons</title>
<link href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/"/>
<updated>2020-07-14T00:00:00Z</updated>
<id>https://moderncss.dev/pure-css-custom-styled-radio-buttons/</id>
<content type="html"><p>Using a combination of the following properties, we can create custom, accessible, cross-browser, theme-able, scalable radio buttons in pure CSS:</p> <ul> <li><code>currentColor</code> for theme-ability</li> <li><code>em</code> units for relative sizing</li> <li><code>appearance: none</code> for full restyling access</li> <li>CSS grid layout to align the input and label</li> </ul> <p><strong>Head's up</strong>: A lot of these styles overlap with the episode on <a href="https://moderncss.dev/pure-css-custom-checkbox-style">custom checkbox styles</a> which you might be interested in reading next!</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Now available</strong>: my egghead video course <a href="https://5t3ph.dev/a11y-forms">Accessible Cross-Browser CSS Form Styling</a>. You'll learn to take the techniques described in this tutorial to the next level by creating a themable form design system to extend across your projects.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="radio-button-html">Radio Button HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#radio-button-html" aria-labelledby="radio-button-html"><span hidden="">#</span></a></div> <p>There are two appropriate ways to layout radio buttons in HTML.</p> <p>The first wraps the <code>input</code> within the <code>label</code>. This implicitly associates the label with the input that its labeling, and also increases the hit area to select the radio.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio label text <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>The second is to have the <code>input</code> and <code>label</code> be siblings and use the <code>for</code> attribute set to the value of the radio's <code>id</code> to create the association.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></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>radio1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Radio label text<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>Our technique will work with either setup, although we're going to select the wrapping label method to prevent including an extra div.</p> <p>The base HTML for our demo including classes and two radios - necessary to test <code>:checked</code> vs. un-checked states - is the following:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</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>form-control<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>radio<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>radio<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> Radio - checked <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span></code></pre> <p>For groups of radio buttons, it is also necessary to provide the same <code>name</code> attribute.</p> <p>Here's how the native HTML elements in Chrome appear:</p> <p><img src="https://moderncss.dev/img/posts/18/radio-chrome-default.png" alt="native radio buttons in Chrome" /></p> <div class="heading-wrapper h2"> <h2 id="common-issues-with-native-radio-buttons">Common Issues with Native Radio Buttons</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#common-issues-with-native-radio-buttons" aria-labelledby="common-issues-with-native-radio-buttons"><span hidden="">#</span></a></div> <p>The primary issue that causes developers to seek a custom styling solution for radio buttons is the variance in their appearance between browsers which is increased when including mobile browsers as well.</p> <p>As an example, here are radio buttons as shown on Mac versions of Firefox (left), Chrome (middle), and Safari (right):</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bnce8hn7xmnc9fibmspf.png" alt="radio buttons in Firefox, Chrome, Safari" /></p> <p>Our solution will accomplish the following goals:</p> <ul> <li>scale with the <code>font-size</code> provided to the <code>label</code></li> <li>gain the same color as provided to the label for ease of theme-ability</li> <li>achieve a consistent, cross-browser design style, including <code>:focus</code> state</li> <li>maintain keyboard and color contrast accessibility</li> </ul> <blockquote> <p>If your primary goal is modifying the <code>:checked</code> state color, you may be interested in learning more about <a href="https://www.smashingmagazine.com/2021/09/simplifying-form-styles-accent-color/">the upcoming <code>accent-color</code> property</a> from Michelle Barker's overview.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="theme-variable-and-box-sizing-reset">Theme Variable and <code>box-sizing</code> Reset</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#theme-variable-and-box-sizing-reset" aria-labelledby="theme-variable-and-box-sizing-reset"><span hidden="">#</span></a></div> <p>There are two base CSS rules that must be placed first in our cascade.</p> <p>First, we create a custom variable called <code>--color</code> which we will use as a simple way to easily theme our radio buttons.</p> <pre class="language-css"><code class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--form-control-color</span><span class="token punctuation">:</span> rebeccapurple<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, we use the universal selector to reset the <code>box-sizing</code> method used to <code>border-box</code>. This means that padding and border will be included in the calculation of any elements computed final size instead of increasing the computed size beyond any set dimensions.</p> <pre class="language-css"><code class="language-css"><span class="token selector">*, *:before, *:after</span> <span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="label-styles">Label Styles</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#label-styles" aria-labelledby="label-styles"><span hidden="">#</span></a></div> <p>Our label uses the class of <code>.form-control</code>. The base styles we'll include here are font styles. Recall from earlier that the <code>font-size</code> will not yet have an effect on the visual size of the radio <code>input</code>.</p> <details open=""> <summary>CSS for ".form-control font styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control</span> <span class="token punctuation">{</span> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-57 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-57"> <input type="radio" name="radio" /> Radio </label> <label class="form-control-57"> <input type="radio" name="radio" checked="" /> Radio - checked </label> </div> </div> <p>We're using an abnormally large <code>font-size</code> just to emphasize the visual changes for purposes of the tutorial demo.</p> <p>Our label is also the layout container for our design, and we're going to set it up to use CSS grid layout to take advantage of <code>gap</code>.</p> <details open=""> <summary>CSS for ".form-control grid layout"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">font-family</span><span class="token punctuation">:</span> system-ui<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 1em auto<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">gap</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-334 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-334"> <input type="radio" name="radio-form-control-334" /> Radio </label> <label class="form-control-334"> <input type="radio" name="radio-form-control-334" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="custom-radio-button-style">Custom Radio Button Style</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#custom-radio-button-style" aria-labelledby="custom-radio-button-style"><span hidden="">#</span></a></div> <p>Ok, this is the part you came here for!</p> <blockquote> <p>The original version of this tutorial demonstrated use of extra elements to achieve the desired effect. Thanks to improved support of <code>appearance: none</code> and with appreciation to <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">Scott O'Hara's post on styling radio buttons and checkboxes</a>, we can rely on pseudo elements instead!</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="step-1-hide-the-native-radio-input">Step 1: Hide the Native Radio Input</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-1-hide-the-native-radio-input" aria-labelledby="step-1-hide-the-native-radio-input"><span hidden="">#</span></a></div> <p>We need to hide the native radio input, but keep it technically accessible to enable proper keyboard interaction and also to maintain access to the <code>:focus</code> state.</p> <p>To accomplish this, we only need to set <code>appearance: none</code>. This removes nearly all inherited browser styles <em>and</em> <strong>gives us access to styling the input's pseudo elements</strong>. Notice we have two additional properties to complete the reset.</p> <details open=""> <summary>CSS for "hiding the native radio input"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* Add if not using autoprefixer */</span> <span class="token property">-webkit-appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* For iOS &lt; 15 to remove gradient background */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token comment">/* Not removed via appearance */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-817 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-817 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-817"> <input type="radio" name="radio-hide-native" /> Radio </label> <label class="form-control-817"> <input type="radio" name="radio-hide-native" checked="" /> Radio - checked </label> </div> </div> <blockquote> <p><strong>Worried about support</strong>? This combination of using <code>appearance: none</code> and the ability to style the input's pseudo elements has been supported since 2017 in Chrome, Safari, and Firefox, and in Edge since their switch to Chromium in May 2020.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="step-2-custom-unchecked-radio-styles">Step 2: Custom Unchecked Radio Styles</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-2-custom-unchecked-radio-styles" aria-labelledby="step-2-custom-unchecked-radio-styles"><span hidden="">#</span></a></div> <p>For our custom radio, we'll update box styles on the base input element. This includes inheriting the font styles to ensure the use of <code>em</code> produces the desired sizing outcome, as well as using <code>currentColor</code> to inherit any update on the label's color.</p> <p>We use <code>em</code> for the <code>width</code>, <code>height</code>, and <code>border-width</code> value to maintain the relative appearance. Good ole <code>border-radius: 50%</code> finishes the expected appearance by rendering the element as a circle.</p> <details open=""> <summary>CSS for "custom unchecked radio styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token selector">.form-control + .form-control</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1em<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-977 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-977 + .form-control-977 { margin-top: 1em; } .form-control-977 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-977"> <input type="radio" name="radio-unchecked-styles" /> Radio </label> <label class="form-control-977"> <input type="radio" name="radio-unchecked-styles" checked="" /> Radio - checked </label> </div> </div> <p>Finally, we slid in a little style to provide some space between our radios by applying <code>margin-top</code> with the help of the <a href="https://moderncss.dev/guide-to-advanced-css-selectors-part-one/#adjacent-sibling-combinator">adjacent sibling combinator</a>;</p> <div class="heading-wrapper h3"> <h3 id="step-3-improve-input-vs-label-alignment">Step 3: Improve Input vs. Label Alignment</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-3-improve-input-vs-label-alignment" aria-labelledby="step-3-improve-input-vs-label-alignment"><span hidden="">#</span></a></div> <p>If you've worked with grid or flexbox, your instinct right now might be to apply <code>align-items: center</code> to optically tune the alignment of the input in relation to the label text.</p> <p>But what if the label is long enough to become broken across multiple lines? In that case, alignment along horizontal center may be undesirable.</p> <p>Instead, let's make adjustments so the input stays horizontally centered in relation to the first line of the label text.</p> <p>On our input, we'll use <code>transform</code> to nudge the element up. This is a bit of a magic number, but as a starting point this value is half the size of the applied border.</p> <details open=""> <summary>CSS for "improve input vs. label alignment"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">font</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">width</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">height</span><span class="token punctuation">:</span> 1.15em<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">border</span><span class="token punctuation">:</span> 0.15em solid currentColor<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>-0.075em<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .form-control-352 { font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-352 + .form-control-352 { margin-top: 1em; } .form-control-352 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-352"> <input type="radio" name="radio-alignment" /> Radio </label> <label class="form-control-352"> <input type="radio" name="radio-alignment" checked="" /> Radio - checked </label> </div> </div> <p>And with that our alignment is complete and functional for both single-line and multi-line labels.</p> <div class="heading-wrapper h3"> <h3 id="step-4-the-checked-state">Step 4: The <code>:checked</code> State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-4-the-checked-state" aria-labelledby="step-4-the-checked-state"><span hidden="">#</span></a></div> <p>It's now time to bring in our <code>::before</code> pseudo element which will be styled in order to represent the <code>:checked</code> state.</p> <blockquote> <p>The <code>:checked</code> naming convention may be a little confusing here, but it is a CSS selector that is available for both radio buttons and checkboxes.</p> </blockquote> <p>We first need to change the display behavior of the input to use grid:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This is the quickest way to align the <code>:before</code> to the horizontal and vertical center of our custom control.</p> <p>Then, we create the <code>:before</code> element, including a transition and using transform hide it with <code>scale(0)</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of <code>box-shadow</code> instead of <code>background-color</code> will enable the state of the radio to be visible when printed (h/t <a href="https://dev.to/alvaromontoro/comment/1214h">Alvaro Montoro</a>).</p> <p>Finally, when the <code>input</code> is <code>:checked</code>, we make it visible with <code>scale(1)</code> with a nicely animated result thanks to the <code>transition</code>. Be sure to click between the radios to see the animation!</p> <details open=""> <summary>CSS for ":checked state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 120ms transform ease-in-out<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 1em 1em <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">input[type="radio"]:checked::before</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-679 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-679 + .form-control-679 { margin-top: 1em; } .form-control-679 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-679 input[type="radio"]::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); } .form-control-679 input[type="radio"]:checked::before { transform: scale(1) !important; } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-679"> <input type="radio" name="radio-checked-state" /> Radio </label> <label class="form-control-679"> <input type="radio" name="radio-checked-state" checked="" /> Radio - checked </label> </div> </div> <h4>High Contrast Themes and Forced Colors</h4> <p>One more state we need to ensure our radio responds to is what you may hear referred to as <a href="https://blogs.windows.com/msedgedev/2020/09/17/styling-for-windows-high-contrast-with-new-standards-for-forced-colors/">&quot;Windows High Contrast Mode&quot; (WHCM)</a>. In this mode, the user's operating system swaps out color-related properties for a reduced palette which is <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors">an incoming part of the CSS spec called &quot;forced-colors&quot;</a>.</p> <p>In this mode, our <code>box-shadow</code> is completely removed, leaving these users without an indicator of the checked state.</p> <p>Fortunately, resolving this involves adding just one extra property. We'll provide a <code>background-color</code>, which is normally removed in forced-colors mode, but will be retained if we use one of the defined forced colors. In this case, we're selecting <code>CanvasText</code> which will match the regular body text color.</p> <p>Due to the style stacking order, our <code>box-shadow</code> that we've themed for use in regular mode is actually visuallly placed <em>over</em> the <code>background-color</code>, meaning we can use both without any further modifications.</p> <details open=""> <summary>CSS for "supporting forced-colors"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]::before</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token comment">/* Windows High Contrast Mode */</span> <span class="token property">background-color</span><span class="token punctuation">:</span> CanvasText<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-23 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-23 + .form-control-23 { margin-top: 1em; } .form-control-23 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-23 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-23 input:checked::before { transform: scale(1); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-23"> <input type="radio" name="radio-forced-colors" /> Radio </label> <label class="form-control-23"> <input type="radio" name="radio-forced-colors" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h3"> <h3 id="step-5-the-focus-state">Step 5: The <code>:focus</code> State</h3> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#step-5-the-focus-state" aria-labelledby="step-5-the-focus-state"><span hidden="">#</span></a></div> <p>Depending on your browser, you may already be seeing some kind of a focus style provided as an <code>outline</code>. We'll add just a tiny bit of customization to make it match our input's color, and provide some space from the input by using <code>outline-offset</code>.</p> <p>This is a simplification from the earlier version of this tutorial which used <code>box-shadow</code>. Now, evergreen browsers all support <code>outline</code> which follows <code>border-radius</code>, removing an excuse not to just use the <code>outline</code>!</p> <blockquote> <p>Remember: <code>:focus</code> is a temporary state, but it's very important that it is highly visible to ensure the accessibility of your form controls and other interactive elements.</p> </blockquote> <details open=""> <summary>CSS for ":focus state styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">input[type="radio"]:focus</span> <span class="token punctuation">{</span> <span class="token property">outline</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> solid currentColor<span class="token punctuation">;</span> <span class="token property">outline-offset</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> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-403 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-403 + .form-control-403 { margin-top: 1em; } .form-control-403 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-403 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-403 input:checked::before { transform: scale(1); } .form-control-403 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-403"> <input type="radio" name="radio-focus-state" /> Radio </label> <label class="form-control-403"> <input type="radio" name="radio-focus-state" checked="" /> Radio - checked </label> </div> </div> <p>And with that, the essential styles for a custom radio button are complete! 🎉</p> <div class="heading-wrapper h2"> <h2 id="experimental-using-focus-within-to-style-the-label-text">Experimental: Using <code>:focus-within</code> to Style the Label Text</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#experimental-using-focus-within-to-style-the-label-text" aria-labelledby="experimental-using-focus-within-to-style-the-label-text"><span hidden="">#</span></a></div> <p>Since the label is not a sibling of the native input, we can't use the <code>:focus</code> state of the input to style it.</p> <p>An upcoming pseudo selector is <code>:focus-within</code>, and one feature is that it can apply styles to elements that contain an element which has received focus.</p> <blockquote> <p>The ModernCSS episode on a <a href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/">pure CSS accessible dropdown navigation menu</a> also covered <code>:focus-within</code>.</p> </blockquote> <p>For now, any critial usage of <code>:focus-within</code> requires a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a>, so the following styles should be considered an enhancement and not relied on as the only way to provide a visual indication of focus.</p> <p>We'll test for focus by adding a rule for <code>:focus-within</code> on the label (<code>.form-control</code>). This means when the native input - which is a child and therefore &quot;within&quot; the label - receives focus, we can style <em>any</em> element within the label while focus is active.</p> <details open=""> <summary>CSS for "experimental :focus-within styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.form-control:focus-within</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--form-control-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .form-control-183 { --form-control-color: rebeccapurple; font-family: system-ui, sans-serif; font-size: 2rem; font-weight: bold; line-height: 1.1; display: grid; grid-template-columns: 1em auto; gap: 0.5em; } .form-control-183 + .form-control-183 { margin-top: 1em; } .form-control-183:focus-within { color: var(--form-control-color); } .form-control-183 input { -webkit-appearance: none; appearance: none; background-color: #fff; margin: 0; font: inherit; color: currentColor; width: 1.15em; height: 1.15em; border: 0.15em solid currentColor; border-radius: 50%; transform: translateY(-0.075em); display: grid; place-content: center; } .form-control-183 input::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 50%; transform: scale(0); transition: 120ms transform ease-in-out; box-shadow: inset 1em 1em var(--form-control-color); background-color: CanvasText; } .form-control-183 input:checked::before { transform: scale(1); } .form-control-183 input:focus { outline: max(2px, 0.15em) solid currentColor; outline-offset: max(2px, 0.15em); } </style> <div class="demo no-resize"> <div class="demo--content demo--place-center"> <label class="form-control-183"> <input type="radio" name="radio-focus-within" /> Radio </label> <label class="form-control-183"> <input type="radio" name="radio-focus-within" checked="" /> Radio - checked </label> </div> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/pure-css-custom-styled-radio-buttons/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Here is the solution altogether in a CodePen that you can fork and experiment with further.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VweBgeZ" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p>Check out the <a href="https://moderncss.dev/pure-css-custom-checkbox-style">custom checkbox styling</a> to also learn how to extend styles to the <code>:disabled</code> state, and see how to work with <code>clip-path</code> as a <code>:checked</code> indicator.</p> </content>
</entry>
<entry>
<title>Announcing Style Stage: A Community CSS Showcase</title>
<link href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/"/>
<updated>2020-07-10T00:00:00Z</updated>
<id>https://moderncss.dev/announcing-style-stage-a-community-css-showcase/</id>
<content type="html"><p>Dear CSS community:</p> <p>I invite you to participate in a new project where you have the opportunity to challenge both your CSS and web design skills while learning in public.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1wnz0c4yyqq03vfrhds1.png" alt="Style Stage: A modern CSS showcase styled by community contributions" /></p> <div class="heading-wrapper h2"> <h2 id="about-style-stage">About Style Stage</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#about-style-stage" aria-labelledby="about-style-stage"><span hidden="">#</span></a></div> <p><a href="https://stylestage.dev/">StyleStage.dev</a> is not just an informational landing page about the project - it is the foundational HTML which is intended to be restyled by contributors - like you!</p> <p>Style Stage started as a wild idea to reanimate the spirit of <a href="http://www.csszengarden.com/">CSS Zen Garden</a> which was created by <a href="http://daveshea.com/projects/zen/">Dave Shea</a> and that provided a demonstration of &quot;what can be accomplished through CSS-based design&quot; until submissions stopped in 2013.</p> <p>Things in CSS-land have improved a lot since then, including the available properties, the tools we have available to build with, our greater understanding of addressing accessibility concerns, and increased awareness of performance impacts.</p> <blockquote> <p>If you missed the launch live stream, here's the lightly edited <a href="https://youtu.be/O2hLsVX5eN0">full recorded Twitch broadcast</a>, or by topic in <a href="https://www.twitch.tv/collections/CZgljEORIBZLxg">the Twitch highlight collection</a>. We covered a lot of the things in this article plus more about CSS, and concluded by building a new 11ty feature ✨.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="why-do-we-need-this">Why Do We Need This?</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#why-do-we-need-this" aria-labelledby="why-do-we-need-this"><span hidden="">#</span></a></div> <p>There's a growing tendency to choose a framework when what would best serve a project is using CSS in its natural state and becoming one with the <a href="https://dev.to/5t3ph/intro-to-the-css-cascade-the-c-in-css-1kh0">cascade</a>.</p> <p>Creating your Style Stage stylesheet will challenge you to explore techniques like flexbox and grid to arrange the page, and pseudo elements to add extra content and flair. Take the opportunity to design something crazy! So far, gradients and <code>transform: skew()</code> are popular with contributors ✨ Check out the <a href="https://stylestage.dev/#about">list of other modern features</a> for inspiration of what you may like to try.</p> <p>By prohibiting access to the HTML (which is already semantic and accessible on its own), Style Stage encourages you to get creative while re-familiarizing yourself with the basics. And in this otherwise fast-paced industry, I see that as a major positive.</p> <p>Play is a powerful teacher! How far can you push the boundaries while staying accessible and performant? These are skills worth practicing that will equip you to choose the right tool for the job in future projects. Even if the right tool is a framework, you will have a deeper understanding of <em>how</em> styles you apply are working and improve your ability to customize them.</p> <p>Trust me - it feels good to say: &quot;I can do that in CSS!&quot;</p> <p>For a growing list of tips, ideas, and inspiration, <a href="https://stylestage.dev/resources/">view the resources</a>.</p> <p><a href="https://stylestage.dev/subscribe/">Subscribe to the newsletter</a> for periodic updates related to new styles and release of new features. You can also pick up the <a href="https://stylestage.dev/feed/">RSS feed</a>.</p> <div class="heading-wrapper h2"> <h2 id="how-do-i-contribute-a-stylesheet">How Do I Contribute a Stylesheet?</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#how-do-i-contribute-a-stylesheet" aria-labelledby="how-do-i-contribute-a-stylesheet"><span hidden="">#</span></a></div> <p>Review <a href="https://stylestage.dev/">StyleStage.dev</a> for expanded details, as well as the source HTML and CSS.</p> <p>By participating as a contributor, your work will be shared with your provided attribution as long as Style Stage is online, your stylesheet link and any asset links remain valid, and all contributor guidelines are adhered to.</p> <p><a href="https://stylestage.dev/#contribute">Review the steps to contribute &gt;</a></p> <div class="heading-wrapper h3"> <h3 id="guidelines-tldr">Guidelines TL;DR</h3> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#guidelines-tldr" aria-labelledby="guidelines-tldr"><span hidden="">#</span></a></div> <p>All submissions will be minified, autoprefixed, and prepended with the <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA license</a> as well as attribution using the metadata you provide. You may use any build setup you prefer, but the final submission should be the compiled, unminified CSS. You retain the copyright to original graphics and must ensure all graphics used are appropriately licensed. All asset links, including fonts, must be absolute to external resources. Stylesheets will be saved into the Github repo, and detected changes that violate the guidelines are cause for removal.</p> <p>Ensure your design is responsive, and that it passes accessible contrast (we'll be using aXe to verify). Animations should be removed via <code>prefers-reduced-motion</code>. Cutting-edge techniques should come with a fallback if needed to not severely impact the user experience. No content may be permanently hidden, and hidden items must come with an accessible viewing technique. Page load time should not exceed 3 seconds.</p> <p><a href="https://stylestage.dev/guidelines/">Review the full guidelines &gt;</a></p> <div class="heading-wrapper h2"> <h2 id="possible-future-features">(Possible) Future Features</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#possible-future-features" aria-labelledby="possible-future-features"><span hidden="">#</span></a></div> <ul> <li><strong>dark mode toggle</strong> - optionally opt into a toggle to improve the user experience by allowing them to choose which theme to display rather than relying on <code>prefers-color-scheme</code> values alone</li> <li><strong>style index preview images</strong> - to improve the experience of browsing available styles</li> </ul> <div class="heading-wrapper h2"> <h2 id="credits">Credits</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#credits" aria-labelledby="credits"><span hidden="">#</span></a></div> <p>Big thanks to <a href="https://twitter.com/hankchizljaw">Andy Bell (@hankchizljaw)</a> for his extra time reviewing the foundations of the project, and being an early promotor! 💫</p> <p>Thanks also to <a href="https://twitter.com/MiriSuzanne">Miriam Suzanne (@MiriSuzanne)</a> for some great feedback and ideas about how to evolve the project 🚀</p> <p>The project description and guidelines were made more clear by suggestions from <a href="https://twitter.com/KatieLangerman">Katie Langerman (@KatieLangerman)</a> 🙌</p> <p>And of course the original six contributors - thanks so much for helping bring this project to life by spending time within a short deadline to create your awesome stylesheets!</p> <div class="heading-wrapper h2"> <h2 id="some-stats">Some Stats</h2> <a class="anchor" href="https://moderncss.dev/announcing-style-stage-a-community-css-showcase/#some-stats" aria-labelledby="some-stats"><span hidden="">#</span></a></div> <ul> <li>The idea for Style Stage arose July 2, 2020 and the project launched July 10, 2020.</li> <li>Built on my favorite static site generator, <a href="https://11ty.dev/">11ty</a> beginning from a starter I developed</li> <li>Hosted on <a href="https://www.netlify.com/">Netlify</a></li> <li>The Main Stage theme receives a 100 lighthouse and PageSpeed score, as well as a speed index of 0.502s 🙌</li> </ul> <p>Here are the original 6 contributors:</p> <ul> <li><a href="https://stylestage.dev/styles/retroish">Retroish</a> by Jean Louise Tiston</li> <li><a href="https://stylestage.dev/styles/skewten">Skewten</a> by Donnie D'Amato</li> <li><a href="https://stylestage.dev/styles/purplify-and-pastel">Purplify &amp; Pastel</a> by Dominic Duffin</li> <li><a href="https://stylestage.dev/styles/vaporwave">Vaporwave</a> by Shannon Crabill</li> <li><a href="https://stylestage.dev/styles/center-stage">Center Stage</a>by Katie Langerman</li> <li><a href="https://stylestage.dev/styles/queer-modes">Queer Modes</a> by Miriam Suzanne</li> </ul> </content>
</entry>
<entry>
<title>3 Popular Website Heroes Created With CSS Grid Layout</title>
<link href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/"/>
<updated>2020-07-02T00:00:00Z</updated>
<id>https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/</id>
<content type="html"><p>This episode explores creating website heroes - aka &quot;headers&quot; - with one of my favorite ways to use CSS grid layout: by turning it into a canvas.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Support notice</strong>: The essential properties used in these techniques - <code>grid-template-areas</code> and <code>object-fit</code> - are not supported below IE 16. Good news - that still means they are about 96% supported!</p> </blockquote> <p>Inspired by my years in marketing, here are the three layouts we're going to create:</p> <p>#1: Marketing Call-to-Action (CTA) and Image</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z56fftywzc8qgq5xm00m.png" alt="preview of marketing hero" /></p> <p>#2: Text Overlay on Background Image</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f5f211a3wfy2ut8ak8xw.png" alt="preview of text overlay hero" /></p> <p>#3: Two-Column with Copy and Form</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hmsmbwiwna1zoaw5dfht.png" alt="preview of two-column hero" /></p> <div class="heading-wrapper h2"> <h2 id="base-html-and-css-grid-setup">Base HTML and CSS Grid Setup</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#base-html-and-css-grid-setup" aria-labelledby="base-html-and-css-grid-setup"><span hidden="">#</span></a></div> <p>In the not-too-distant past, the way to achieve most of these layouts required the use of <code>position: absolute</code>.</p> <p>With grid, we can upgrade from that solution and gain responsive, dynamic positioning superpowers!</p> <p>Here's our starting point for HTML:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">></span></span> <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>hero__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>You really need this product, so hurry and buy it today!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Buy Now<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://placecorgi.com/600<span class="token punctuation">"</span></span> <span class="token attr-name">alt</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 punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span></code></pre> <p>Then, we'll turn the <code>header</code> into a grid container, and create a single template area called &quot;hero&quot;:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"hero"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of the template area creates a single named grid cell. We then create a rule to assign all children of any type (thanks to the universal selector <code>*</code>) to this area:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-is-this-magic">What is this magic?</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#what-is-this-magic" aria-labelledby="what-is-this-magic"><span hidden="">#</span></a></div> <p>Using CSS grid layout template areas means that we get all the goodness of grid positioning which is a big upgrade from absolute positioning!</p> <p>This directs that all children share the same grid cell, effectively turning it into a canvas.</p> <p>We can now define items be centered or other positions relative to each other and the container <em>instead of</em> doing math to calculate percentages, or encountering media query headaches to get around absolute positioning interfering with responsive growing and shrinking of content.</p> <p>Read on to gain more context with our header examples!</p> <div class="heading-wrapper h2"> <h2 id="hero-1-marketing-call-to-action-cta-and-image">Hero #1: Marketing Call-to-Action (CTA) and Image</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-1-marketing-call-to-action-cta-and-image" aria-labelledby="hero-1-marketing-call-to-action-cta-and-image"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z56fftywzc8qgq5xm00m.png" alt="preview of marketing hero" /></p> <p>With no other styles yet in place besides our base, here's what we have: the elements are aligned top left, with the image layered over the <code>.hero__content</code>:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dg2w0otsiww9bxpl79oq.png" alt="initial state of the base marketing hero" /></p> <p>The first thing we'll address is setting some dimension expectations on the header:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 65vh<span class="token punctuation">;</span> <span class="token property">max-height</span><span class="token punctuation">:</span> 600px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Viewport units such as <code>vh</code> are my go-to way to size heroes. This keeps them proportionate to the users viewing area by dynamically sizing them up or down depending on device size.</p> <p>We are capping this particular one to prevent the image resolution from getting too stretched by way of <code>max-height</code>, but that is optional and circumstantial to the image in use.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Next, we need to provide some direction on the <code>img</code> behavior.</p> <p>You may be wondering why we didn't use a background image. The first answer is so that the image retains its semantics including the <code>alt</code> attribute in order to be discoverable by assistive technology.</p> <p>Second, keeping it as an image allows more flexibility in how we style and position it.</p> <p>We will use <code>object-fit</code> together with <code>object-position</code> which actually makes its initial behavior very similar to that of a background image:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">object-position</span><span class="token punctuation">:</span> 5vw -5vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>65vh<span class="token punctuation">,</span> 600px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 60%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>height: min(65vh, 600px)</code> is important, because it directs it to fill the height of the <code>header</code> based on the &quot;minimum&quot; of either of those values, which comes from the heights we set on the base <code>header</code>. After giving explicit dimension parameters, <code>object-fit</code> takes over and scales the image contents to &quot;cover&quot; the dimensions including the <code>width: 60%</code>.</p> <blockquote> <p><strong>New to</strong> <code>object-fit</code>? Check out <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">episode 3 on responsive images</a> or <a href="https://moderncss.dev/animated-image-gallery-captions-with-bonus-ken-burns-effect/">episode 6 on animated image captions</a> for more examples.</p> </blockquote> <p>Finally, we will add <code>justify-self</code> to the <code>img</code> to define that it should be placed at the <code>end</code> of the container - our first dip into the magic of using grid for this solution:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's our progress:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/m4aq7uhkgnfeld50esns.png" alt="marketing hero progress with image styles" /></p> <p>Now for the <code>.hero__content</code>, the first improvement is to give it a width definition, and also give it some space from the viewport edge:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 35%<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 30ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Since our <code>img</code> is allowed a width of 60%, we don't want our combined <code>margin</code> and <code>width</code> to exceed 40% in order to avoid overlap.</p> <p>We also provided a <code>min-width</code> to keep a reasonable amount of space for the content as the viewport shrinks.</p> <p>Now we can again leverage the use of grid, and return to our <code>header</code> rule to add an alignment property:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This vertically aligns the content with the image. Since the image is set to 100% of the <code>header</code> height, optically this vertically centers the content, resulting in our desktop-ready hero:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mywia1t4o2qsd5wwedqk.png" alt="marketing hero desktop finalized" /></p> <p>In order for this to continue working on the smallest screens, we need a couple tweaks.</p> <p>First, we'll default the image width to 80% and wrap the 60% reduction in a media query. We'll also add a transition just to smooth it between viewport resizes:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">width</span><span class="token punctuation">:</span> 80%<span class="token punctuation">;</span> <span class="token comment">// &lt; update</span> <span class="token property">transition</span><span class="token punctuation">:</span> 180ms width ease-in<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 60rem<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 60%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Then on the content, we'll use a bit of trickery to set the background to an alpha of the hero background so it's only visible once it begins to overlap the image, and include an update on the margin, some padding, and a bit of <code>border-radius</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem 0 1rem 5%<span class="token punctuation">;</span> <span class="token comment">// &lt; update</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token function">mix</span><span class="token punctuation">(</span>#fff<span class="token punctuation">,</span> <span class="token variable">$primary</span><span class="token punctuation">,</span> 97%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5rem 0.5rem 0.5rem 0<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We <em>did</em> have to add one little <code>z-index</code> there to bring it above the <code>img</code>, but it wasn't too painful! 😊</p> <p>Here's the final mobile-sized viewport result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/za98lm6kb4vr7aniumrj.png" alt="marketing hero mobile finalized" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-1">Summary of Techniques in Hero #1</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-1" aria-labelledby="summary-of-techniques-in-hero-1"><span hidden="">#</span></a></div> <ul> <li><code>object-fit</code> used to control <code>img</code> size</li> <li><code>align-items: center</code> used to vertically align the grid children</li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-1-demo">Hero #1 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-1-demo" aria-labelledby="hero-1-demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="eYJGJVg" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="hero-2-text-overlay-on-background-image">Hero #2: Text Overlay on Background Image</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-2-text-overlay-on-background-image" aria-labelledby="hero-2-text-overlay-on-background-image"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f5f211a3wfy2ut8ak8xw.png" alt="preview of text overlay hero" /></p> <p>For this version with our base HTML and CSS styles, the image completely obscures the content since it's a jpg and therefore has no alpha.</p> <p>So step 1: bring the content above the image:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, we'll define header dimensions:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">height</span><span class="token punctuation">:</span> 60vh<span class="token punctuation">;</span> <span class="token property">max-height</span><span class="token punctuation">:</span> 600px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And again, we'll use <code>object-fit</code> to control our <code>img</code>. The difference this time is we want it to span 100% width <em>and</em> height so it has full coverage over the header:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">min</span><span class="token punctuation">(</span>60vh<span class="token punctuation">,</span> 600px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Before we show a progress shot, let's adjust the alignment of the grid children:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here's the result so far:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/35l09zkti0xhmsxaolf9.png" alt="progress of hero #2" /></p> <p>It's quite apparent that the contrast of the text is not sufficient over the background image. One common way to both add an extra touch of branding and also aid in addressing contrast issues is to apply a color screen to an image.</p> <p>Here's our slight hack to accomplish this - first, the <code>header</code> receives a <code>background-color</code> that includes alpha transparency:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$primary</span></span><span class="token punctuation">:</span> #3c87b3<span class="token punctuation">;</span> <span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token variable">$primary</span><span class="token punctuation">,</span> 0.7<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we direct the image to slip behind the header with <code>z-index</code>. In my testing, this still keeps the <code>img</code> discoverable with assistive tech, but reach out if you know of an issue!</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">img </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">z-index</span><span class="token punctuation">:</span> -1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Resulting in the following:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/zszictap7muu2qlvy67r.png" alt="base of hero two completed" /></p> <p>To demonstrate a bit more about what is possible thanks to using grid, let's create a <code>:before</code> and <code>:after</code> pseudo-element on the <code>header</code> to hold an SVG pattern.</p> <p>The important thing to include is to also assign the pseudo-elements to <code>grid-area: hero</code>. Otherwise, they would slot in as new &quot;rows&quot; according to default grid flow, which would break our canvas.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>::before </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 50vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 50vmin<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>-10%<span class="token punctuation">,</span> -10%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url">url</span><span class="token punctuation">(</span><span class="token string">"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{svgColor($support)}' fill-opacity='0.6' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>::after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> hero<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 60vmin<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>20%<span class="token punctuation">,</span> 40%<span class="token punctuation">)</span> <span class="token function">rotate</span><span class="token punctuation">(</span>45deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url">url</span><span class="token punctuation">(</span><span class="token string">"data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{svgColor($support)}' fill-opacity='0.6' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And due to the <code>place-items: center</code> definition, here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/j15c0cwopacooq9w2bgx.png" alt="hero with pseudo-elements added" /></p> <p>The first issue to resolve is the overflow, which we'll fix with:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">header </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, grid offers <code>self</code> properties to direct that specific item can reposition itself, which breaks it from the grid parent definition. So we'll update our pseudo-elements accordingly:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>::before </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-self</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>::after </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">place-self</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And with that, we've completed hero 2! Test out the demo to see that the small viewport version continues to work well:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/urq97jy17bipfg5du00t.png" alt="completed hero #2" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-2">Summary of Techniques in Hero #2</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-2" aria-labelledby="summary-of-techniques-in-hero-2"><span hidden="">#</span></a></div> <ul> <li>created a color screen over the <code>img</code> by defining <code>background-color</code> of the <code>header</code> with <code>rgba</code> and adding <code>z-index: -1</code> to the <code>img</code> to slide it behind the <code>header</code></li> <li>used pseudo-elements for additional design flair, and positioned them separately from the parent grid definition with <code>place-self</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-2-demo">Hero #2 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-2-demo" aria-labelledby="hero-2-demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="GRoMxor" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="hero-3-two-column-with-copy-and-form">Hero #3: Two-Column with Copy and Form</h2> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-3-two-column-with-copy-and-form" aria-labelledby="hero-3-two-column-with-copy-and-form"><span hidden="">#</span></a></div> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hmsmbwiwna1zoaw5dfht.png" alt="preview of two-column hero" /></p> <p>For this third example, our base HTML changes a bit to add in the form. We also include a wrapper around the main content which we'll explain soon:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">></span></span> <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>hero__wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>hero__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Product<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>You really need this product, so hurry and buy it today!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>hero__form<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Subscribe to Our Updates<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Enter your email:<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</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>email<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>email<span class="token punctuation">"</span></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>email<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</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>button<span class="token punctuation">"</span></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>submit<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Subscribe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span></code></pre> <p>And here's our starting appearance, given use of things we've already learned: the <code>header</code> SVG pattern pseudo-element has already used <code>place-self: end</code>, the form styles are already in-tact (spoiler: that is using grid too!), and overflow is also already being controlled:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/q4k3y3y16vim19lpxqsa.png" alt="starting hero #3 appearance" /></p> <p>Let's start to fix this by beginning our <code>.hero__wrapper</code> class. An important update is to set its width to <code>100vw</code> so that as a containing element it spans the header entirely. We'll also go ahead and create it as a grid container:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100vw<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next, it's time to define the grid columns. We'll use my favorite technique which is already featured in multiple episodes for intrinsically responsive grid columns:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>30ch<span class="token punctuation">,</span> auto<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p><strong>Learn more</strong> about this technique in episode 8: <a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/">Solutions to Replace the 12-Column Grid</a></p> </blockquote> <p>We've used <code>auto</code> for the max-allowed width instead of <code>1fr</code> since we do not want equal columns, but rather for the columns to expand proportionately to their relative size. This is for a bit better visual balance between the text content and the form, and can be adjusted to taste. If you desire equal-width columns, use <code>1fr</code> instead of <code>auto</code>.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/l755c2tx2e1dv5tyi6gb.png" alt="hero #3 with grid-template-columns applied" /></p> <p>Let's talk a minute about that bottom gradient border - how is it being positioned?</p> <p>It is the <code>:after</code> element on the <code>header</code> and is the primary reason we are using a wrapper around the main header content. It is being positioned with <code>place-self: end</code>, and its width is due to the natural stretch behavior. Check the demo to see how minimal its style are.</p> <p>Ok, now we need some additional spacing around the content. In the other heroes, we applied a <code>height</code> but this doesn't quite cover our use case here because on smaller viewports the form and content will vertically stack.</p> <p>Instead, this is a better job for good ole <code>padding</code>. We'll place it on <code>.hero__wrapper</code> so as not to affect the position of the SVG pattern or gradient border:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10vmin 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Use of the viewport unit <code>vmin</code> for the top and bottom padding means that the smaller of &quot;view-width&quot; or &quot;view-height&quot; will be used for that value. The benefit here is helping ensure the hero doesn't cover the entire screen of smaller viewports, which may make it seem like there isn't additional page content. This is because in that case the &quot;veiw-width&quot; will be used making it a smaller value versus on larger, desktop viewports where it will use &quot;view-height&quot; and be a greater value.</p> <p>To complete the large viewport appearance, we will add two positioning values to the wrapper:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Where <code>align-items</code> provides vertical alignment, and <code>justify-content</code> provides horizontal alignment.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/rolfl5if4yy3hcs6yji7.png" alt="completed large viewport appearance for hero #3" /></p> <p>On smaller viewports, our only adjustment is to ensure that the content remains legible over the SVG pattern. We'll use a similar technique to hero #1:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.hero__wrapper </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.hero__content </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span><span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$primary</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> 90%<span class="token punctuation">)</span><span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jx9m8ftlhd2gwzix3yp3.png" alt="hero #3 with mobile adjusted styles" /></p> <div class="heading-wrapper h3"> <h3 id="summary-of-techniques-in-hero-3">Summary of Techniques in Hero #3</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#summary-of-techniques-in-hero-3" aria-labelledby="summary-of-techniques-in-hero-3"><span hidden="">#</span></a></div> <ul> <li>use of a wrapper to provide a secondary grid layout for content versus <code>header</code> design elements</li> <li>creation of auto-width columns with <code>grid-template-columns</code></li> <li>leveraging <code>vmin</code> to minimize padding on smaller viewports and increase it for larger viewports</li> </ul> <div class="heading-wrapper h3"> <h3 id="hero-3-demo">Hero #3 Demo</h3> <a class="anchor" href="https://moderncss.dev/3-popular-website-heroes-created-with-css-grid-layout/#hero-3-demo" aria-labelledby="hero-3-demo"><span hidden="">#</span></a></div> <p><em>Bonus</em>: use of <code>clamp</code> to shrink the paragraph copy proportionate to the viewport size in order to reduce it for smaller viewports.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="oNboWLP" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>3 CSS Grid Techniques to Make You a Grid Convert</title>
<link href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/"/>
<updated>2020-06-27T00:00:00Z</updated>
<id>https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/</id>
<content type="html"><p>CSS grid layout can feel daunting. In fact, I avoided it for several years and was a <em>diehard</em> flexbox fan.</p> <p>Then I found the following 3 powerful properties/techniques in grid that completely changed my tune.</p> <p>Spoiler, here's a tweet with all of them. Keep reading to learn a bit more!</p> <p><a href="https://twitter.com/5t3ph/status/1276898582113681409" class="button button--small">View tweet</a></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="1-switch-the-grid-flow-axis">1: Switch the Grid Flow Axis</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#1-switch-the-grid-flow-axis" aria-labelledby="1-switch-the-grid-flow-axis"><span hidden="">#</span></a></div> <p>I first desired this behavior when I wanted X-axis alignment of variable width items, and also desired to leverage <code>gap</code>.</p> <div class="heading-wrapper h3"> <h3 id="the-code">The Code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code" aria-labelledby="the-code"><span hidden="">#</span></a></div> <pre class="language-css"><code class="language-css"><span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does" aria-labelledby="what-it-does"><span hidden="">#</span></a></div> <p>Default grid flow is oriented to &quot;row&quot; layout, which is complementary to block layout, where items flow down the page along the Y-axis.</p> <p>This switches that default behavior to &quot;column&quot; which means items default to flowing along the X-axis.</p> <div class="heading-wrapper h3"> <h3 id="things-to-note">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note" aria-labelledby="things-to-note"><span hidden="">#</span></a></div> <ul> <li>items will take as much room as needed to contain their content <em>up until</em> the max width of the container, at which point text will break to new lines</li> <li>there is a risk of overflow because of lack of &quot;wrapping&quot; behavior in grid, which means assigning this property will flow things along the X-axis into infinity <ul> <li>this can be solved by only applying this behavior above a certain viewport width via a media query</li> </ul> </li> </ul> <blockquote> <p><strong>Note</strong>: once flexbox <code>gap</code> is fully supported, it will likely be the better method for this outcome due to also having wrapping behavior</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use" aria-labelledby="when-to-use"><span hidden="">#</span></a></div> <p>For short content where variable widths are desirable, such as a navbar or list of icons, and when wrapping either isn't a concern or a media query can be used to flip this property.</p> <div class="heading-wrapper h2"> <h2 id="2-xy-center-anything">2. XY Center Anything</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#2-xy-center-anything" aria-labelledby="2-xy-center-anything"><span hidden="">#</span></a></div> <p>Literally the thing everyone makes fun of when CSS comes up as a topic:</p> <blockquote> <p>&quot;How do you center a div?&quot;</p> </blockquote> <p>Grid has the easiest answer!</p> <blockquote> <p>Psst - interested in <em>lots</em> of solutions to centering? Check out the &quot;<a href="https://moderncss.dev/complete-guide-to-centering-in-css/">The Complete Guide to Centering in CSS</a>&quot;</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="the-code-1">The code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code-1" aria-labelledby="the-code-1"><span hidden="">#</span></a></div> <pre class="language-css"><code class="language-css"><span class="token property">place-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does-1">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does-1" aria-labelledby="what-it-does-1"><span hidden="">#</span></a></div> <p>Centers any child content both vertically (Y) and horizontally (X) 🙌</p> <div class="heading-wrapper h3"> <h3 id="things-to-note-1">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note-1" aria-labelledby="things-to-note-1"><span hidden="">#</span></a></div> <ul> <li>there are some <a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-grid-solution">gotchas</a> related to the behavior assigned to the children</li> <li>the visual appearance may be only that it's centered horizontally if the container's height doesn't exceed the height of the children</li> </ul> <div class="heading-wrapper h3"> <h3 id="when-to-use-1">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use-1" aria-labelledby="when-to-use-1"><span hidden="">#</span></a></div> <p>Anytime you want to center something vertically and horizontally.</p> <div class="heading-wrapper h2"> <h2 id="3-intrinsically-responsive-grid-columns">3. Intrinsically Responsive Grid Columns</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#3-intrinsically-responsive-grid-columns" aria-labelledby="3-intrinsically-responsive-grid-columns"><span hidden="">#</span></a></div> <div class="heading-wrapper h3"> <h3 id="the-code-2">The code</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#the-code-2" aria-labelledby="the-code-2"><span hidden="">#</span></a></div> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--grid-col-breakpoint</span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.grid-columns </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span> auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--grid-col-breakpoint<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="what-it-does-2">What it does</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#what-it-does-2" aria-labelledby="what-it-does-2"><span hidden="">#</span></a></div> <p>The unique-to-grid functions of <code>repeat()</code> and <code>minmax()</code> along with the <code>auto-fit</code> keyword work together to create an outcome where immediate children become equal-width, responsive columns.</p> <p>As the grid container resizes, upon hitting the supplied value for <code>--grid-col-breakpoint</code>, the columns begin to drop to a new virtual row.</p> <p>Use of the <code>--grid-col-breakpoint</code> CSS variables allows altering this &quot;breakpoint&quot; via inline style to accommodate various content within a layout or across components with only a single class.</p> <div class="heading-wrapper h3"> <h3 id="things-to-note-2">Things to note</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#things-to-note-2" aria-labelledby="things-to-note-2"><span hidden="">#</span></a></div> <ul> <li>columns will always be equal width, growing and shrinking to remain equal as the container also flexes in size</li> <li>it's possible for orphan columns to exist at certain container widths</li> </ul> <blockquote> <p>Check out a more comprehensive explanation of what's happening in &quot;<a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid">Solutions to Replace the 12-Column Grid</a>&quot;</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="when-to-use-2">When to use</h3> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#when-to-use-2" aria-labelledby="when-to-use-2"><span hidden="">#</span></a></div> <p>If you're in need of an intrinsically responsive grid with equal-width columns.</p> <p>This behavior is also one of two viable &quot;container query&quot; methods that we currently have available since it responds to container width instead of being controlled by media queries.</p> <blockquote> <p>Learn more about the idea of using <a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#grid-solution">grid for container queries</a> &gt;</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/3-css-grid-techniques-to-make-you-a-grid-convert/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>This CodePen demonstrates all 3 techniques:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="gOPxxNO" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>Check out my <a href="https://5t3ph.dev/egghead">egghead lessons</a> which further explore these grid techniques</p> </blockquote> </content>
</entry>
<entry>
<title>Expanded Use of `box-shadow` and `border-radius`</title>
<link href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/"/>
<updated>2020-06-21T00:00:00Z</updated>
<id>https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/</id>
<content type="html"><p>This episode will explore expanded usage of <code>box-shadow</code> and <code>border-radius</code> and conclude with a landing page demo using these properties to enhance the image presentation.</p> <p>You will learn:</p> <ul> <li>the expanded syntax of <code>border-radius</code>, and when to use which type of units to set values</li> <li>how to create multiple <code>box-shadow</code> layers</li> <li>about the <code>box-shadow</code> value <code>inset</code></li> <li>how to &quot;hack&quot; <code>box-shadow</code> with <code>inset</code> to apply popular image filter techniques</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="overview-of-border-radius">Overview of <code>border-radius</code></h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#overview-of-border-radius" aria-labelledby="overview-of-border-radius"><span hidden="">#</span></a></div> <p>Kids these days will never have to deal with creating a gif for each corner of a div that you want to appear rounded 😂 Truly, the release and eventual support of <code>border-radius</code> was one of the most significant milestones in CSS.</p> <p>For a refresher, here's how it's often used:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span></code></pre> <p>Which for a square element will result in a circle appearance.</p> <p>Or scale it back for just a slight roundedness to otherwise sharply square corners, such as for a button or card where you might use:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span></code></pre> <p>One way to begin to take this a bit further is to use two values, where the first value sets top-left and bottom-right, and the second value sets top-right and bottom-left:</p> <pre class="language-css"><code class="language-css"><span class="token property">border-radius</span><span class="token punctuation">:</span> 20% 50%<span class="token punctuation">;</span></code></pre> <p>Whiiiich... kind of looks like a lemon?</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1f7t6sztody2bx49wyru.png" alt="example of two value border-radius" /></p> <p>Now, given the same values but a different size div those results will vary quite widely:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/a23blvdcovhwkg3990l3.png" alt="same border-radius value applied to various size divs" /></p> <p>We can also set all four corners:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* top-left | top-right | bottom-right | bottom-left */</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 3vw 4vw 8vw 2vw<span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/987sbc9xlb92hmvu88ai.png" alt="example with all four corners set on border-radius" /></p> <p>But wait - there's more!</p> <p>There's a super uber expanded syntax which allows you to define both the horizontal and vertical radius each corner is rounded by. Whereas the default is rounded very circularly, adding the additional radius alters the &quot;clipping&quot; that occurs to produce the corner, allowing the trendy &quot;blob&quot; shapes:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/17ejae2bvf386oml7fm2.png" alt="&quot;blob&quot; shape with border-radius expanded syntax" /></p> <blockquote> <p>Check out this <a href="https://codepen.io/jh3y/pen/XWmvwYg">border-radius playground</a> by Jhey with a config panel and live preview to generate expanded syntax <code>border-radius</code> styles.</p> </blockquote> <p>There are a few more ways to define the syntax, you can check those out on <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius">the MDN docs</a>.</p> <div class="heading-wrapper h3"> <h3 id="units-for-border-radius">Units for <code>border-radius</code></h3> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#units-for-border-radius" aria-labelledby="units-for-border-radius"><span hidden="">#</span></a></div> <p>Notice that we've used a few different units: <code>%</code>, <code>px</code>, and <code>vw</code> which is the &quot;viewport width&quot; viewport unit.</p> <p><strong>Percentage</strong> values are relative to the size of the element which means less predictability if the element is expected to be various sizes. For the <code>50%</code> example, once the element is no longer square it begins to appear more elliptical.</p> <p><strong>Absolute units</strong> such as <code>px</code> or <code>rem</code> are preferred when you want consistent results not based on the element but based on perhaps design specs.</p> <p><strong>Relative units</strong> such as viewport units or <code>em</code> can be beneficial if you want consistency but also an element of scale:</p> <ul> <li>viewport units will <em>compute</em> to be larger on &quot;desktop&quot; and smaller on &quot;mobile&quot; but remain consistent in rounded appearance</li> <li><code>em</code> will vary based on <code>font-size</code>, resulting in more rounded corners on elements with larger fonts and less rounded corners on elements with smaller fonts</li> </ul> <div class="heading-wrapper h2"> <h2 id="overview-of-box-shadow">Overview of <code>box-shadow</code></h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#overview-of-box-shadow" aria-labelledby="overview-of-box-shadow"><span hidden="">#</span></a></div> <p>Personally, upon it reaching decent support, the thing that made <code>box-shadow</code> the most exciting for me was for popping models off the page in a far more native fashion 🙏</p> <p>And for awhile, they were <em>mission critical</em> for what we called &quot;skeumorphic design&quot;. And they made a bit of a comeback over the past year for &quot;neumorphic design&quot;.</p> <blockquote> <p>Were those new terms to you? Here's <a href="https://uxdesign.cc/neumorphism-in-user-interfaces-b47cef3bf3a6">a nice overview</a> comparing each with examples by UX Collective.</p> </blockquote> <p>But what I usually use <code>box-shadow</code> for these days is just a subtle hint at elevation for things like buttons or cards:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* offset-x | offset-y | blur-radius | spread-radius | color */</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 3px 4px 5px 0px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.38<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/gngoroh4e552tie3giuf.png" alt="demo of basic box-shadow" /></p> <p>It is acceptable to omit the <code>blur-radius</code> and <code>spread-radius</code>, which leads to a sharper shadow due to loss of blur. The shadow will also not extend beyond the offset values since the spread is intended to scale beyond the element's dimensions.</p> <p>In the above example, we essentially gave the shadow a &quot;light source&quot; that was slightly above and left of the element which &quot;cast&quot; the shadow slightly right and below.</p> <p>Instead, you can set the offsets to 0 for a shadow that is equal around the element, although at least <code>blur-radius</code> is required. If <code>spread-radius</code> is also supplied, that will apply scale to the shadow to extend it beyond the element's dimensions:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0.25em 0.25em <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5i9920x1vi8s1ncllkdv.png" alt="no offset box-shadow" /></p> <p>I enjoy using <code>box-shadow</code> to provide a custom visual <code>:focus</code> state on buttons. Unlike <code>border</code>, <code>box-shadow</code> does <em>not</em> alter the elements computed dimensions, meaning adding or removing it does not cause either it or elements around it to shift from reflow. Check out <a href="https://moderncss.dev/css-button-styling-guide/">the episode on buttons</a> to see that method.</p> <p>A unique feature of <code>box-shadow</code> is the ability to apply multiples:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> 2px 4px 0 4px yellowgreen<span class="token punctuation">,</span> 4px 8px 0 8px pink<span class="token punctuation">,</span> 8px 10px 0 12px navy<span class="token punctuation">;</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hzpsvyd4u3qoo8oob6d5.png" alt="multiple box-shadow values" /></p> <p>The &quot;stacking order&quot; is such that the first set is &quot;on top&quot;, or visually closest to the element, and down from there. That's why the <code>spread-radius</code> has to be increased, else the &quot;lower&quot; layers would not be visible (except where the offset doesn't overlap).</p> <p>We can also flip <code>box-shadow</code> to the <em>inside</em> of the element by adding the <code>inset</code> value:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 12px 4px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.8<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>These values will apply a very literal &quot;inset&quot; shadow appearance, where the element appears &quot;sunk&quot; into the page:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/pg2npjg4kh7rgoj5zsto.png" alt="inset box-shadow" /></p> <div class="heading-wrapper h3"> <h3 id="box-shadow-hacks-for-images"><code>box-shadow</code> Hacks for Images</h3> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#box-shadow-hacks-for-images" aria-labelledby="box-shadow-hacks-for-images"><span hidden="">#</span></a></div> <p>Two alternate ways I like to use <code>inset</code> shadows are for images.</p> <p><strong>The key is in the stacking order</strong> and the fact <code>box-shadow</code> is placed <em>above</em> images applied via <code>background-image</code>.</p> <h4>Vignette</h4> <p>The first is as a &quot;vignette&quot; which is a photography technique where the edges of the photo softly fade into the background. This helps highlight the subject of the photo particularly if the subject is centrally located.</p> <p>However, the <code>inset</code> property cannot be directly used on an <code>&lt;img/&gt;</code> since it is considered an &quot;empty&quot; element, not a container element.</p> <p>Going with a <code>background-image</code> instead, the following CSS is applied to the container:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.vignette-container</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 30vmin<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 4vmin 3vmin <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's a comparison where the left image has the vignette applied and the right does not.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hotopx3kxs7c5gj3zvj7.png" alt="two images where left image has vignette applied" /></p> <p>We used <code>vmin</code> units so that the <code>box-shadow</code> would scale relative to the overall image size which was also set in <code>vmin</code>. This is due to the inability for <code>box-shadow</code> to be set using percent, which makes it a little difficult to set values relative to the element. So the hack within a hack is to use viewport units to set expected size of the element to have more predictable results for the <code>box-shadow</code> across viewport sizes.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <h4>Image Color Screen</h4> <p>I found this technique when I was using <code>background-image</code> but also wanted a color &quot;screen&quot; - which means needing to place a translucent color fill over the image. This is a helpful technique to help defend the contrast of any text placed over background images, and also to create visual consistency among an otherwise unrelated set of images.</p> <p>My previous solution - used for many years - was an extra absolutely positioned <code>:before</code> on the containing element with the screen color applied there as <code>background-color</code>.</p> <p>Here's the new <code>inset</code> <code>box-shadow</code> solution:</p> <pre class="language-css"><code class="language-css"><span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 0 100vmax <span class="token function">rgba</span><span class="token punctuation">(</span>102<span class="token punctuation">,</span> 76<span class="token punctuation">,</span> 202<span class="token punctuation">,</span> 0.65<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And a comparison with the screen on the left and without on the right:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nv0in4wlzaobgfqwa7sz.png" alt="screen comparison images" /></p> <p>Did you catch the hack? I mentioned that percentages aren't allowed - but since viewport units are, we set the <code>spread</code> to <code>100vmax</code> which is <em>likely</em> to cover any element unless your element is greater than <em>double</em> the max-width or max-height of the viewport.</p> <p>Hold up - why <em>double</em>? Because the <code>spread</code> is created from <em>all sides</em> of the element so it's at least double the <code>vmax</code> value. You can test it out by using the above value and setting your container to at least <code>width: 100vw; height: 100vh</code> and see at what point the middle of the container is not covered.</p> <p>If you're using it for something more than a hero image, for example, just increase as needed, such as to <code>200vmax</code>.</p> <p><em>If anyone is aware of taking performance hits for this, let me know!</em></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/expanded-use-of-box-shadow-and-border-radius/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>The demo goes a bit further and shows how to use <code>object-fit</code> on images to make them <em>behave</em> like <code>background-image</code> while still retaining the semantics of an image, which is great when use of the <code>alt</code> property is necessary (spoiler: you should go this route most of the time for accessibility).</p> <p>In addition, the <code>h1</code> headline has <code>text-shadow</code> applied, which is animated on <code>:hover</code> of the <code>header</code>. The main difference between <code>box-shadow</code> and <code>text-shadow</code> is that <code>text-shadow</code> does not have a <code>spread</code> property.</p> <p>It also combines techniques by showing <code>border-radius</code> in conjunction with <code>box-shadow</code> for the content images. And, the content images show how <code>box-shadow</code> can be animated by pulling back the vignette fade on <code>:hover</code> for a highlighting effect.</p> <p>The extra trick on the <code>box-shadow</code> animation is re-supplying the <code>inset</code> values to ensure the pull-back of the fade uses our translucent black instead of white. This is because <code>box-shadow</code> in most browsers (except Safari) defaults to the value of the current color if not explicitly supplied, and the list items have set <code>color: #fff</code>.</p> <p><strong>Bonus</strong>: My favorite &quot;<code>position:absolute</code> killer&quot; using CSS grid and assigning all elements to the same <code>grid-area</code> - maybe a future episode and/or <a href="https://5t3ph.dev/egghead">egghead video</a> will cover that 😉</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="ExPZNRZ" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Container Query Solutions with CSS Grid and Flexbox</title>
<link href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/"/>
<updated>2020-06-11T00:00:00Z</updated>
<id>https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/</id>
<content type="html"><p>True container queries are a much asked for CSS feature that would be a complement to media queries but be placed on container elements instead of the viewport.</p> <blockquote> <p><strong>Experimental CSS container queries are here!</strong> <a href="https://css.oddbird.net/rwd/query/explainer/">Here's Miriam Suzanne's container queries proposal explainer</a> which has an experimental prototype in Chrome Canary. After you download Canary, visit <code>chrome://flags</code> to search for and enable container queries. For more info, review my <a href="https://www.smashingmagazine.com/2021/05/complete-guide-css-container-queries/">primer on container queries</a>.</p> </blockquote> <p>Using grid and flexbox, we can create styles that respond to <em>container</em> and <em>content</em> widths and overcome some of the pain points that container queries are proposed to resolve.</p> <p>We'll cover:</p> <ul> <li>⏸ Use of CSS grid layout and flexbox to achieve container dependent adjustments from equal-width columns to row layout</li> <li>🏆 The &quot;holy grail&quot; solution to variable-width breakpoint columns</li> <li>🚀 Implementing CSS custom variables to make the solutions as scalable as possible</li> </ul> <p><a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#solutions">Skip to the solutions &gt;</a></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="understanding-the-problem">Understanding the Problem</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#understanding-the-problem" aria-labelledby="understanding-the-problem"><span hidden="">#</span></a></div> <p>When Ethan Marcotte introduced the concept of <a href="https://alistapart.com/article/responsive-web-design/">Responsive Design</a>, we became trained to adjust elements on the page across viewport sizes by using media queries.</p> <p>I am a huge fan of Ethan's and recall reading that article within weeks of publish. It was easily one of the most transformative articles for the web.</p> <blockquote> <p>P.S. - Responsive Design recently turned 10! <a href="https://ethanmarcotte.com/wrote/responsive-design-at-10/">Read Ethan's reflection &gt;</a></p> </blockquote> <p>Ten years ago, media queries made sense because we've always been bound to the tools available at the time.</p> <p>But there remained one area where CSS didn't quite have an answer: how do you respond to changes within an individual container on the page instead of the whole viewport?</p> <p>This gave rise to the 12-column grid and related frameworks such as Bootstrap as an intermediary solution of applying width adjustments on the fly without writing an every growing wall of one-off media queries.</p> <p>And those addressed a lot of common frustrations, and almost got us there.</p> <p>As a veteran of marketing website development, the biggest downside of relying on grid frameworks was still being bound to the tool. We would <em>rarely</em> intentionally design outside of a 12-col grid because of the cost of overhead in developing additional styles and wrangling custom media queries.</p> <p>Too much blah blah blah for ya? Let's see <a href="https://getbootstrap.com/docs/4.5/layout/grid/#how-it-works">an example from Bootstrap docs</a>:</p> <pre class="language-html"><code class="language-html"><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>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>col-sm<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>One of three columns<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>The key here is the <code>col-sm</code> classes which are required to switch from &quot;equal width columns&quot; above a set &quot;small&quot; viewport width (540px for Bootstrap) to full-width on even smaller viewports.</p> <p>But - what if these columns were <em>within</em> another column?</p> <p><strong>This is where we hit our issue</strong>: The <code>col-sm</code> columns won't go full-width until the <em>viewport</em> is reduced in size. Which means, the nested columns could be extremely narrow. To resolve this, you may have to add a bunch of extra utility classes to switch, switch, and possibly even switch again.</p> <p>Wouldn't it be nice if those columns were somehow aware of their content, and break based on a minimum <em>content</em> width rather than rely on <em>viewport</em> width?</p> <p>Welcome to the need for container queries 🙌</p> <blockquote> <p><strong>Note</strong>: The comparable solutions presented for grid vs. flexbox, unfortunately, are restricted to equal column widths, but this still helps reduce the need for media queries and fill in the gap for behavior to be based on container vs. viewport width. <a href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#holy-grail-variable-width-breakpoint-columns">Skip to the &quot;holy grail&quot; solution</a> for variable width columns.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="solutions">Solutions</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#solutions" aria-labelledby="solutions"><span hidden="">#</span></a></div> <p>We will look at how to handle container queries with grid and flexbox as well as discuss the pros and cons of each.</p> <p><strong>Preview</strong></p> <p>To further provide context for what we're looking to achieve, here's the final result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/msf5471wc2brbd5t9cio.gif" alt="demo of dashboard with container queries" /></p> <p>Using only <em>three</em> classes and <em>three</em> CSS <code>vars()</code> - one of each for grid and each of the flexbox solutions - we have made the &quot;cards&quot; responsively switch from 1-3 across, <em>and</em> the card content switch from column to row layout with no media queries in sight 😎</p> <div class="heading-wrapper h3"> <h3 id="grid-solution">Grid Solution</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#grid-solution" aria-labelledby="grid-solution"><span hidden="">#</span></a></div> <blockquote> <p><strong>New to CSS grid layout</strong>? We're going to revisit a method that I've also described in <a href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/">Solutions to Replace the 12-Column Grid</a> and that is explored in two of my <a href="https://5t3ph.dev/egghead">egghead videos</a> related to responsive grids.</p> </blockquote> <p>We'll begin by creating the <code>.grid</code> class, setting the display, and adding a modest gap value:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>After that, we just need one line to initiate our container query magic:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>20ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>repeat</code> function will apply the defined column behavior to all columns that exist. Right away, this makes it scalable to grow to any number of columns that could be created from any type of content.</p> <p>Then, instead of an absolute number, we use the <code>auto-fit</code> value which is responsible for ensuring the columns stay equal-width by stretching columns to fill any available space.</p> <p>After that, we use <code>minmax()</code> to set the minimum allowed column width, and then use <code>1fr</code> as the max which ensures the content fills the column as much as room allows.</p> <p><code>minmax()</code> and in particular the <code>20ch</code> is where we essentially have defined what in media query land would be our &quot;breakpoint&quot;.</p> <p>The <code>ch</code> unit is equal to the <code>0</code> (zero) character of the current font, which makes this extra sensitive to the current content.</p> <p>You could certainly swap to <code>rem</code> to prevent the computed value from changing as fonts change. However, in the demo this value technically does use the <code>font-size</code> applied to <code>body</code> which would be equivalent to <code>1rem</code> if you haven't changed it. That's because it's placed on the <code>ul</code> and not typography elements, so the <code>ul</code> defaults to inheriting <code>font</code> properties from the <code>body</code>.</p> <p>The only unit you should <em>not</em> use would be <code>%</code> as that would prevent the columns from ever collapsing.</p> <p>Cool, that takes care of the &quot;cards&quot;:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/f68jidz6v92fvisk5ram.gif" alt="demo of responsive grids columns" /></p> <h4>Grid Container Content</h4> <p>Now to handle for the card content which we would also like to allow to be displayed in columns if room allows. But, the &quot;breakpoint&quot; should be smaller.</p> <p>At first, you may reach for a utility class. In fact, I did too.</p> <p>But modern CSS gives us a more flexible way: custom variables.</p> <p>First, we have to set our initial variable value, which is assigned to <code>:root</code>. The value we are setting is the <code>min</code> part of <code>minmax</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--grid-min</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And then update our rule accordingly:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--grid-min<span class="token punctuation">)</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>We can then add our <code>.grid</code> class to the <code>li</code> which is our &quot;card&quot; container, and then use inline style to modify the <code>--grid-min</code> value:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>card grid<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--grid-min</span><span class="token punctuation">:</span> 15ch</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Jujubes soufflé cake tootsie roll sesame snaps cheesecake bonbon.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Halvah bear claw cheesecake. Icing lemon drops chupa chups pudding tiramisu. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></code></pre> <p>Resulting in our final grid solution. Notice how the card content independently adjusts from column to row layout as the card container narrows or widens.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ntxgbnub2i5dkgvkwjwq.gif" alt="demo of final grid container query solution" /></p> <p><img src="https://media.giphy.com/media/NiOPyn6a7tV3q/giphy.gif" alt="woman crying and saying &quot;it's so beautiful&quot;" /></p> <h4>Why Can't Grid Do Variable Width Columns?</h4> <p>What is the barrier preventing this solution handling variable width columns while maintaining the &quot;container query&quot; benefits of breaking to row layout without media queries?</p> <p>The closest we can get to variable width columns with grid in the easiest way possible is to remove our previous work, and instead use:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span></code></pre> <p>Which flips the default axis and by default the created columns are indeed variable width.</p> <p>However, this can't ever break down because there is no <code>wrap</code> property in grid. This means you will likely encounter overflow, which can be acceptable for predictably short content. It can also be placed within a media query to only trigger that behavior above a certain viewport width - which again, is opposite to the goal of &quot;container queries&quot;.</p> <p>I was very hopeful that our solution could be extended to this use case with:</p> <pre class="language-html"><code class="language-html">style="--grid-min: min-content;"</code></pre> <p>Which would compute to updating our property definition to:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>min-content<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>In theory, this seems like a near-perfect solve. Typically <code>min-content</code> means the content would be allowed to shrink to the <em>minimum</em> width required to hold the content (in a text block, this essentially means shrinking as far as the longest word).</p> <p>Unfortunately, <a href="https://drafts.csswg.org/css-grid/#repeat-syntax">the <code>repeat</code> spec</a> specifically prohibits this behavior:</p> <blockquote> <p>Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.</p> </blockquote> <p>Where <code>min-content</code> is considered to be one of the &quot;intrinsic sizes&quot;. Bummer.</p> <p>Read on to learn how flexbox can provide this behavior!</p> <div class="heading-wrapper h3"> <h3 id="flexbox-solution-1">Flexbox Solution #1</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#flexbox-solution-1" aria-labelledby="flexbox-solution-1"><span hidden="">#</span></a></div> <p>The first flexbox solution I'm going to describe is an example of a technique created by Heydon Pickering called the &quot;<a href="https://heydonworks.com/article/the-flexbox-holy-albatross-reincarnated/">Flexbox Albatross</a>&quot;.</p> <p>Let's begin our rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flexbox </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The notable thing here is ensuring <code>flex-wrap: wrap</code> is set, else the &quot;breakpoint&quot; effect would never actually occur.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Now, a big difference between flexbox and grid layout is that the sizing behavior of the flex children is <em>not</em> set on the parent.</p> <p>To make our rule the most flexible, we will use the child combinator - <code>&gt;</code> - in addition to the universal sector - <code>*</code> - to begin a rule that will be applied to immediate flex children of any element type.</p> <p>Using Sass, we can neatly nest this under the previous properties:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flexbox </span><span class="token punctuation">{</span> <span class="token comment">// ...existing rules</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// flex children rules</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Here's where the &quot;Flexbox Albatross&quot; magic happens. Let's add the rules and then discuss.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">flex-grow</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span>35rem <span class="token operator">-</span> 100%<span class="token punctuation">)</span> <span class="token operator">*</span> 999<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><code>flex-grow: 1</code> ensures that the column will fill as much space as the algorithm and other property values will allow.</p> <p>The <code>flex-basis</code> rule performs some math magic with the CSS property <code>calc</code> that essentially leads to the element being at minimum <code>35rem</code> and below that minimum expanding to <code>100%</code>.</p> <p>The result is equal-width columns up until the minimum acceptable width.</p> <blockquote> <p>Unfortunately, <code>calc</code> does not allow the <code>ch</code> value which takes away a bit of the ability to visualize when the column will break. In this demo, <code>35rem</code> was found to be <em>nearly</em> equivalent to <code>20ch</code> in the given font and size.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="creating-gap">Creating <code>gap</code></h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#creating-gap" aria-labelledby="creating-gap"><span hidden="">#</span></a></div> <p>As of writing, the flexbox <code>gap</code> property is gaining support but it's not quite reliably available yet.</p> <p>We will adjust our rules to use margin as a polyfill for now.</p> <blockquote> <p>Did you know: margins <em>do not</em> collapse on flexbox or grid children, so any supplied value will compound between children.</p> </blockquote> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem -0.5rem<span class="token punctuation">;</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>These rules add <code>.5rem</code> around each child, the outer portions of which is negated with a negative margin on the <code>.flex</code> parent.</p> <h4>Adjusting Breakpoint</h4> <p>Unlike grid, this base solution means that all columns will &quot;break&quot; at the same time:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/x41918h42a1y04js0dl2.gif" alt="demo of flexbox albatross" /></p> <p>That is, until we add our friend CSS variables ✨</p> <p>Let's add the variable and update <code>flex-basis</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Update on `:root`</span> <span class="token property">--flex-min</span><span class="token punctuation">:</span> 35rem<span class="token punctuation">;</span> <span class="token comment">// Update in `.flexbox`</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--flex-min<span class="token punctuation">)</span> <span class="token operator">-</span> 100%<span class="token punctuation">)</span> <span class="token operator">*</span> 999<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Now, we'll update it on the middle &quot;card&quot;:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>card<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 50rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></code></pre> <p>Annndddd - on resize, wait a minute - all three cards break at the same time, just earlier than before 🤔</p> <p>What's going on?</p> <p><code>flex-basis: 1</code> + the number of items is to blame.</p> <p>Once the middle card drops, the other two cards expand full-width thanks to <code>flex-basis: 1</code>.</p> <p>If we move our <code>--flex-min</code> adjustment to the <em>first</em> or _last card, then the remaining two cards keep their smaller &quot;breakpoint&quot;.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/heagdzygy7dg661oye98.gif" alt="demo of last card breaking at the adjusted min width" /></p> <p><img src="https://media.giphy.com/media/WqX0mS1ZEtxTcI8bw6/giphy.gif" alt="little girl rolling her eyes and waving her hands in a &quot;whatever&quot; gesture" /></p> <h4>Flexbox Container Content</h4> <p>Ok, now let's address the same idea of paragraph content that switches from column to row layout.</p> <p>With our <code>--flex-min</code> variable already in place, we have what we need.</p> <p>However, with the &quot;gotcha&quot; we just experienced, we will need to add a nested wrapper around the flex children's content:</p> <pre class="language-html"><code class="language-html"><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>flex<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 18rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Jujubes soufflé cake tootsie roll sesame snaps cheesecake bonbon.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Halvah bear claw cheesecake. Icing lemon drops chupa chups pudding tiramisu. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre> <p>This essentially resets the context so it can't affect the parent containers. A minor annoyance compared to grid, but achieves nearly identical functionality:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v5hqhgt9yz4ijrvnif4l.gif" alt="demo of flexbox content container queries" /></p> <div class="heading-wrapper h3"> <h3 id="flexbox-solution-2">Flexbox Solution #2</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#flexbox-solution-2" aria-labelledby="flexbox-solution-2"><span hidden="">#</span></a></div> <p><em>If</em> you will not need multiple unique breakpoints for the flexbox items, we can instead use what Una named the &quot;<a href="https://1linelayouts.glitch.me/">Deconstructed Pancake</a>&quot; to uniformly apply breakpoints with only <code>flex-basis</code>.</p> <p>Benefits of this technique over the Flexbox Albatross:</p> <ul> <li>the <code>flex-basis</code> breakpoint is based on the <em>individual item</em> instead of the <em>width allotted to the parent</em></li> <li>items will start new rows independent of each other vs. all at once without needing to define unique breakpoints</li> <li>we can use <code>ch</code> because there's no <code>calc</code> involved</li> </ul> <p>One potential downside is the need for nested elements to resolve conflicts between setting of the width custom variable (<code>--pancake-min</code>), as shown on the demo when trying to set a new breakpoint for the card paragraph content.</p> <p>Here's the essential CSS:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">:root </span><span class="token punctuation">{</span> <span class="token property">--pancake-min</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.flex-pancake </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 1rem -0.5rem<span class="token punctuation">;</span> <span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 1 1 <span class="token function">var</span><span class="token punctuation">(</span>--pancake-min<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>This looks similar to how we setup the Flexbox Albatross solution up until we get to the <code>flex</code> rule for the children.</p> <p>The shorthand including the computed variable breaks out to:</p> <pre class="language-css"><code class="language-css"><span class="token property">flex-grow</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-shrink</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> 20ch<span class="token punctuation">;</span></code></pre> <p>We're allowing items to both grow and shrink until the area is too narrow for <em>all</em> items to fit based on the <code>flex-basis</code>, at which point the items will begin to drop to new rows. This makes the behavior similar to the grid solution <em>except</em> the dropped item will expand to fill the available area.</p> <p><em>This solution has become my preference in most cases</em>.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d1eyf7oen2yy6q17c8tm.gif" alt="demo of the deconstructed pancake technique" /></p> <div class="heading-wrapper h3"> <h3 id="holy-grail-variable-width-breakpoint-columns">Holy Grail: Variable Width Breakpoint Columns</h3> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#holy-grail-variable-width-breakpoint-columns" aria-labelledby="holy-grail-variable-width-breakpoint-columns"><span hidden="">#</span></a></div> <p>Flexbox will allow us to create a method to designate that <em>some columns</em> should shrink to their &quot;auto&quot; width, while other columns have independent &quot;min-width&quot; behavior. This results in variable width columns that ultimately retain &quot;breakpoint&quot; behavior based on container width.</p> <p>Two keys that enable flexbox as the solution:</p> <ul> <li>&quot;column&quot; width is able to be independently set on flex children</li> <li>&quot;wrapping&quot; is inherently triggered when content exceeds the available horizontal width which is based on the container</li> </ul> <p>We will create a utility class to assign the &quot;auto width&quot; behavior:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">> * </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>.flex--auto </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 0 1 auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>This shorthand computes to:</p> <pre class="language-css"><code class="language-css"><span class="token property">flex-grow</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">flex-shrink</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">flex-basis</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span></code></pre> <p>Resulting in the behavior of only growing to what grid would consider <code>max-content</code> and being allowed to shrink indefinitely.</p> <p>Coupled with other flex children that use the flexbox behavior, the <code>.flex--auto</code> items will <em>appear</em> to break into row behavior once their siblings actually break from hitting their allowed minimum width.</p> <p>To illustrate this, we can setup the following inside one of our existing &quot;cards&quot;. Notice the update on the <code>--flex-min</code> value. That can be adjusted to taste depending on content presented, and it will only apply to flex children without <code>.flex--auto</code>. We could move it to be applied on the <code>span</code> within the <code>li</code> to adjust more exclusively if needed.</p> <pre class="language-html"><code class="language-html"><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>list-unstyled<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--flex-min</span><span class="token punctuation">:</span> 8rem<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Ice Cream<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Butter Pecan<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Musical Artist<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Justin Timberlake<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>flex<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</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>flex--auto<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Painter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>Vincent Van Gogh<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <p><em>Note this also works with the Deconstructed Pancake method.</em></p> <p>And here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o2hhpaqb9jxhsyrfxbhb.gif" alt="demo of holy grail behavior" /></p> <p>You may feel this example is a little jarring, but it illustrates how each list item has independent wrapping behavior once the non-auto item hits the <code>8rem</code> minimum width due to the container width shrinking.</p> <p>More practically, this is also applied on the icon + title lockup in the demo. The demo also shows the same list using the albatross behavior to provide a way to compare the methods.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <blockquote> <p>I encourage you to open this up in CodePen to be able to manipulate the viewport size.</p> </blockquote> <p>The top row (green outline) uses the grid solution, the middle row (red outline) uses the Flexbox Albatross solution, and the bottom row (purple outline) uses the Deconstructed Pancake solution. The second card list for each flexbox solution demonstrates the &quot;holy grail&quot; solution per-list item.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="PoZqEVG" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="when-to-use-each-method">When to Use Each Method</h2> <a class="anchor" href="https://moderncss.dev/container-query-solutions-with-css-grid-and-flexbox/#when-to-use-each-method" aria-labelledby="when-to-use-each-method"><span hidden="">#</span></a></div> <p><strong>Choose <code>grid</code> if</strong>:</p> <ul> <li>Equal-width columns with more &quot;reader friendly&quot; minimum width settings are a priority (<code>ch</code> vs. <code>rem</code>)</li> <li>It's more desirable for columns to break a bit more independently when they arrive at the minimum acceptable width</li> <li>&quot;Orphan&quot; columns for odd numbers of columns are acceptable (seen on the demo at a mid-size viewport)</li> </ul> <p><strong>Choose <code>flex</code> if</strong>:</p> <ul> <li>You need variable width columns that still have &quot;breakpoint&quot; behavior based on container size</li> <li>Lack of full <code>gap</code> support is not an issue</li> </ul> <p><strong>Choose the Flexbox Albatross if</strong>:</p> <ul> <li>It's acceptable for columns to hit the breakpoint simultaneously</li> <li>Fallout of adjustment of that breakpoint is acceptable (possibility of extra columns breaking to rows as described)</li> <li>You want the <code>flex-basis</code> breakpoint to be the <em>parent's width</em></li> </ul> <p><strong>Choose the Deconstructed Pancake if</strong>:</p> <ul> <li>You want the <code>flex-basis</code> breakpoint based on the <em>item</em> width</li> <li>You want items to break to new rows on an individual basis</li> <li>You want to use CSS units like <code>ch</code> that aren't allowed in <code>calc</code></li> </ul> </content>
</entry>
<entry>
<title>Generating `font-size` CSS Rules and Creating a Fluid Type Scale</title>
<link href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/"/>
<updated>2020-05-28T00:00:00Z</updated>
<id>https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/</id>
<content type="html"><p>Let's take the mystery out of sizing type. Typography is both foundational to any stylesheet and the quickest way to elevate an otherwise minimal layout from drab to fab. If you're looking for type design theory or how to select a font, that's outside the scope of this article. The goal for today is to give you a foundation for developing essential type styles in CSS, and terms to use if you wish to explore any topics deeper.</p> <p>This episode covers:</p> <ul> <li>recommended units for <code>font-size</code></li> <li>generating ratio-based <code>font-size</code> values with Sass</li> <li>recommended properties to prevent overflow from long words/names/URLs</li> <li>defining viewport-aware fluid type scale rules with <code>clamp()</code></li> <li>additional recommendations for dealing with type</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="defining-type-scale">Defining &quot;Type Scale&quot;</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#defining-type-scale" aria-labelledby="defining-type-scale"><span hidden="">#</span></a></div> <p>The simplified definition: &quot;type scale&quot; for the web refers to properties such as <code>font-size</code>, <code>line-height</code>, and often <code>margin</code>, that work together to create <em>vertical rhythm</em> in your design. These can be arbitrarily selected (&quot;it just looks good&quot;), or be based on the idea of a &quot;modular scale&quot; that employs ratio-based sizing.</p> <p>At a minimum, it involves setting a base <code>font-size</code> and <code>line-height</code> on <code>body</code> which elements such as paragraphs and list items will inherit by default.</p> <p>Then, set <code>font-size</code> and <code>line-height</code> on heading elements, particularly levels <code>h1</code>-<code>h4</code> for general use.</p> <h4>What about <code>h5</code> and <code>h6</code>?</h4> <p>Certain scenarios might make it beneficial to care about levels 5 and 6 as well, but <strong>it's important to not use a heading tag when it's really a visual style that's desired</strong>. Overuse of heading tags can cause noise or generally impart poor information hierarchy for assistive tech when another element would be better suited for the context.</p> <div class="heading-wrapper h2"> <h2 id="selecting-a-unit-for-font-size">Selecting a Unit for <code>font-size</code></h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#selecting-a-unit-for-font-size" aria-labelledby="selecting-a-unit-for-font-size"><span hidden="">#</span></a></div> <p><code>px</code>, <code>%</code>, <code>rem</code>, and <code>em</code>, oh my!</p> <p>The first upgrade is to forget about <code>px</code> when defining typography. It is not ideal due to failure to scale in proportion to the user's default <code>font-size</code> that they may have set as a browser preference or by using zoom.</p> <p>Instead, it's recommended that your primary type scale values are set with <code>rem</code>.</p> <p>Unless a user changes it, or you define it differently with <code>font-size</code> on an <code>html</code> rule, the default <code>rem</code> value is 16px with the advantage of responding to changes in operating system zoom level.</p> <p>In addition, the value of <code>rem</code> will not change no matter how deeply it is nested, which is largely what makes it the preferred value for typography sizing.</p> <p>A few years ago, you might have started to switch your <code>font-size</code> values to <code>em</code>. Let's learn the difference.</p> <p><code>em</code> will stay proportionate to the element or nearest ancestor's <code>font-size</code> rule. One <code>em</code> is equal to the <code>font-size</code>, so by default, this is equal to <code>1rem</code>.</p> <p>Compared to <code>rem</code>, <code>em</code> can compound from parent to child, causing adverse results. Consider the following example of a list where the <code>font-size</code> is set in <code>em</code> and compounds for the nested lists:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNVeLdM" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>Learn more about units including <code>rem</code> and <code>em</code> in my <a href="https://dev.to/5t3ph/guide-to-css-units-for-relational-spacing-1mj5">Guide to CSS Units for Relative Spacing</a></p> </blockquote> <p><code>em</code> shines when the behavior of spacing relative to the element is the desired effect.</p> <p>One use case is for <a href="https://moderncss.dev/css-button-styling-guide/">buttons</a> when sizing an icon relative to the button text. Use of <code>em</code> will scale the icon proportionate to the button text without writing custom icon sizes if you have variants in the button size available.</p> <p>Percentages have nearly equivalent behavior to <code>em</code> but typically <code>em</code> is still preferred when relative sizing is needed.</p> <div class="heading-wrapper h3"> <h3 id="calculating-px-to-rem">Calculating <code>px</code> to <code>rem</code></h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#calculating-px-to-rem" aria-labelledby="calculating-px-to-rem"><span hidden="">#</span></a></div> <p>I've spent my career in marketing and working with design systems, so I can relate to those of you being given px-based design specs :)</p> <p>You can create calculations by assuming that <code>1rem</code> is <code>16px</code> - or use an <a href="https://borderleft.com/toolbox/rem/">online calculator</a> to do the work for you!</p> <div class="heading-wrapper h2"> <h2 id="baseline-type-styles">Baseline Type Styles</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#baseline-type-styles" aria-labelledby="baseline-type-styles"><span hidden="">#</span></a></div> <p>A solid starting point is to define:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">body </span><span class="token punctuation">{</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Older recommendations may say to use <code>100%</code>, and this article previously recommended <code>1rem</code>. However, the only element the <code>body</code> can inherit from is <code>html</code> which is where the <code>rem</code> unit takes its value and so defining it here is redundant.</p> <p>So, we'll only add one rule which is for accessibility. It is recommended to have a minimum of 1.5 <code>line-height</code> for legibility. This can be affected by various factors, particularly font in use, but is the recommended starting value.</p> <div class="heading-wrapper h3"> <h3 id="preventing-text-overflow">Preventing Text Overflow</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#preventing-text-overflow" aria-labelledby="preventing-text-overflow"><span hidden="">#</span></a></div> <p>We can add some future-proof properties to help prevent overflow layout issues due to long words, names, or URLs.</p> <p>This is optional, and you may prefer to scope these properties to component-based styles or create a utility class to more selectively apply this behavior.</p> <p>We'll scope these to headings as well as <code>p</code> and <code>li</code> for our baseline:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">p, li, h1, h2, h3, h4 </span><span class="token punctuation">{</span> <span class="token comment">// Help prevent overflow of long words/names/URLs</span> <span class="token property">overflow-wrap</span><span class="token punctuation">:</span> break-word<span class="token punctuation">;</span> <span class="token comment">// Optional, not supported for all languages</span> <span class="token property">hyphens</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>As of testing for this episode, <code>overflow-wrap: break-word;</code> seemed sufficient, whereas looking back on articles over the past few years seem to recommend more properties for the same effect.</p> <p>The <code>hyphens</code> property is still <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/hyphens#Browser_compatibility">lacking in support</a>, particularly when you may be dealing with multi-language content. However, it gracefully falls back to simply no hyphenation in which case <code>overflow-wrap</code> will still help. More testing may be required for certain types of content where long words are the norm, ex. scientific/medical content.</p> <p><a href="https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/">This CSS-Tricks article</a> covers additional properties in-depth if you do find these two properties aren't quite cutting it.</p> <div class="heading-wrapper h2"> <h2 id="ratio-based-type-scales">Ratio-based Type Scales</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#ratio-based-type-scales" aria-labelledby="ratio-based-type-scales"><span hidden="">#</span></a></div> <p>While I introduced this episode by saying we wouldn't cover type design theory, we will use the concept of a &quot;type scale&quot; to efficiently generate <code>font-size</code> values.</p> <p>Another term for ratio-based is &quot;modular&quot;, and here's a great article introducing the term by <a href="https://alistapart.com/article/more-meaningful-typography/">Tim Brown on A List Apart</a>.</p> <p>There are some named ratios available, and our Codepen example creates a Sass map of them for ease of reference:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$type-ratios</span></span><span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token string">"minorSecond"</span><span class="token punctuation">:</span> 1.067<span class="token punctuation">,</span> <span class="token string">"majorSecond"</span><span class="token punctuation">:</span> 1.125<span class="token punctuation">,</span> <span class="token string">"minorThird"</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">,</span> <span class="token string">"majorThird"</span><span class="token punctuation">:</span> 1.25<span class="token punctuation">,</span> <span class="token string">"perfectFourth"</span><span class="token punctuation">:</span> 1.333<span class="token punctuation">,</span> <span class="token string">"augmentedFourth"</span><span class="token punctuation">:</span> 1.414<span class="token punctuation">,</span> <span class="token string">"perfectFifth"</span><span class="token punctuation">:</span> 1.5<span class="token punctuation">,</span> <span class="token string">"goldenRatio"</span><span class="token punctuation">:</span> 1.618<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p><em>These ratios were procured from the really slick online calculator <a href="https://type-scale.com/">Type Scale</a></em></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="generating-font-size-using-a-selected-ratio">Generating <code>font-size</code> Using a Selected Ratio</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#generating-font-size-using-a-selected-ratio" aria-labelledby="generating-font-size-using-a-selected-ratio"><span hidden="">#</span></a></div> <p>Stick with me - I don't super enjoy math, either.</p> <p>The good news is we can use Sass to do the math and output styles dynamically in relation to any supplied ratio 🙌</p> <blockquote> <p>Unfamiliar with Sass? It's a preprocessor that gives your CSS superpowers - like variables, array maps, functions, and loops - that compile to regular CSS. <a href="https://sass-lang.com/guide">Learn more about Sass &gt;</a></p> </blockquote> <p>There are two variables we'll define to get started:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Recommended starting point</span> <span class="token property"><span class="token variable">$type-base-size</span></span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token comment">// Select by key of map, or use a custom value</span> <span class="token property"><span class="token variable">$type-size-ratio</span></span><span class="token punctuation">:</span> <span class="token function">type-ratio</span><span class="token punctuation">(</span><span class="token string">"perfectFourth"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>$type-size-ratio</code> is selecting the <code>perfectFourth</code> ratio from the map previewed earlier, which equals <code>1.333</code>.</p> <p><em>The CodePen demo shows how the <code>type-ratio()</code> custom Sass function is created to retrieve the ratio value by key. For use in a single project, you can skip adding the map entirely and directly assign your chosen ratio decimal value to</em> <code>$type-size-ratio</code>.</p> <p>Next, we define the heading levels that we want to build up our type scale from. As discussed previously, we will focus on <code>h1</code>-<code>h4</code>.</p> <p>We create a variable to hold a list of these levels so that we can loop through them in the next step.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// List in descending order to prevent extra sort function</span> <span class="token property"><span class="token variable">$type-levels</span></span><span class="token punctuation">:</span> 4<span class="token punctuation">,</span> 3<span class="token punctuation">,</span> 2<span class="token punctuation">,</span> 1<span class="token punctuation">;</span></code></pre> <p>These are listed starting with <code>4</code> because <code>h4</code> should be the smallest - and closest to the body size - of the heading levels.</p> <p>Time to begin our loop and add the math.</p> <blockquote> <p>Before I lose you, I want to let you know that in an updated tutorial we <a href="https://moderncss.dev/container-query-units-and-fluid-typography/">use custom properties and no Sass</a> to create this same result. Plus, we upgrade to using container query units for fluid typography that is independent of the viewport!</p> </blockquote> <p>First, we create a variable that we will update on each iteration of the loop. To start with, it uses the value of <code>$type-base-size</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$level-size</span></span><span class="token punctuation">:</span> <span class="token variable">$type-base-size</span><span class="token punctuation">;</span></code></pre> <p>If you are familiar with Javascript, we are creating this as essentially a <code>let</code> scoped variable.</p> <p>Next, we open our <code>@each</code> loop and iterate through each of the <code>$type-levels</code>. We compute the <code>font-size</code> value / re-assign the <code>$level-size</code> variable. This compounds <code>$level-size</code> so that is scales up with each heading level and is then multiplied by the ratio for the final <code>font-size</code> value.</p> <pre class="language-scss"><code class="language-scss"><span class="token keyword">@each</span> <span class="token selector"><span class="token variable">$level</span> in <span class="token variable">$type-levels</span> </span><span class="token punctuation">{</span> <span class="token property"><span class="token variable">$level-size</span></span><span class="token punctuation">:</span> <span class="token variable">$level-size</span> <span class="token operator">*</span> <span class="token variable">$type-size-ratio</span><span class="token punctuation">;</span> <span class="token comment">// Output heading styles</span> <span class="token comment">// Assign to element and create utility class</span> <span class="token selector">h<span class="token variable">#{$level}</span> </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token variable">$level-size</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Given the <code>perfectFourth</code> ratio, this results in the following <code>font-size</code> values:</p> <pre class="language-bash"><code class="language-bash">h4: <span class="token number">1</span>.333rem h3: <span class="token number">1</span>.776889rem h2: <span class="token number">2</span>.368593037rem h1: <span class="token number">3</span>.1573345183rem</code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/j42cx5vf0fm9ucp0nqnc.png" alt="preview of 'perfectFourth' type sizes" /></p> <p><em>Example phrase shamelessly borrowed from Google fonts 🙃</em></p> <p><em>h/t to this David Greenwald article on <a href="https://www.rawkblog.com/2018/05/modular-scale-typography-with-css-variables-and-sass/">Modular Scale Typography</a> which helped connect the dots for me on getting the math correct for ratio-based sizing. He also shows how to accomplish this with CSS <code>var()</code> and <code>calc()</code></em></p> <div class="heading-wrapper h3"> <h3 id="line-height-and-vertical-spacing"><code>line-height</code> and Vertical Spacing</h3> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#line-height-and-vertical-spacing" aria-labelledby="line-height-and-vertical-spacing"><span hidden="">#</span></a></div> <p>At a minimum, it would be recommended to include a <code>line-height</code> update within this loop. The preview image already included this definition, as without it, large type generally doesn't fare well from inherits the <code>1.5</code> rule.</p> <p><a href="https://hugogiraudel.com/2020/05/18/using-calc-to-figure-out-optimal-line-height/">A recent article</a> by Jesús Ricarte is very timely from our use case, which proposes the following clever calculation:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">line-height</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>2px <span class="token operator">+</span> 2ex <span class="token operator">+</span> 2px<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>ex</code> unit is intended to be equivalent to the <code>x</code> height of a font. Jesús tested a few solutions and devised the <code>2px</code> buffers to further approach an appropriate <code>line-height</code> that is able to scale. It even scales with fluid - aka &quot;responsive&quot; type - which we will create next.</p> <p>As for vertical spacing, if you are using a CSS reset it may include clearing out all or one direction of margin on typography elements for you.</p> <p>Check via Inspector to see if your type is still inheriting margin styles from the browser. If so, revisit the rule where we handled overflow and add <code>margin-top: 0</code>.</p> <p>Then, in our heading loop, my recommendation is to add:</p> <pre class="language-scss"><code class="language-scss"><span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0.65em<span class="token punctuation">;</span></code></pre> <p>As we learned, <code>em</code> is relative to the <code>font-size</code>, so by using it as the unit on <code>margin-bottom</code> we will achieve space that is essentially 65% of the <code>font-size</code>. You can experiment with this number to your taste, or explore the vast sea of articles that go into heavier theory on vertical rhythm in type systems to find your preferred ideal.</p> <div class="heading-wrapper h2"> <h2 id="fluid-type-aka-responsive-typography">Fluid Type - aka Responsive Typography</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#fluid-type-aka-responsive-typography" aria-labelledby="fluid-type-aka-responsive-typography"><span hidden="">#</span></a></div> <p>If you choose a ratio that results in rather large <code>font-size</code> on the upper end, you are likely to experience overflow issues on small viewports despite our earlier attempt at prevention.</p> <p>This is one reason techniques for &quot;fluid type&quot; have come into existence.</p> <p>Fluid type means defining the <code>font-size</code> value in a way that responds to the viewport size, resulting in a &quot;fluid&quot; reduction of size, particularly for larger type.</p> <p>There is a singular modern CSS property that will handle this exceptionally well: <code>clamp</code>.</p> <p>The <code>clamp()</code> function takes three values. Using it, we can set a minimum allowed font size value, a scaling value, and a max allowed value. This effectively creates a range for each <code>font-size</code> to transition between, and it will work thanks to viewport units.</p> <blockquote> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/clamp()">Learn more about <code>clamp()</code> on MDN</a>, and <a href="https://caniuse.com/css-math-functions">check browser support</a> (currently 90.84%)</p> </blockquote> <p>We'll leave our existing loop in place because we still want the computed ratio value. And, the <code>font-size</code> we've set will become the fallback for browsers that don't yet understand <code>clamp()</code>.</p> <p>But - we have to do more math 😊</p> <p>In order to correctly perform the math, we need to do a bit of a hack (thanks, Kitty at <a href="https://css-tricks.com/snippets/sass/strip-unit-function/">CSS-Tricks</a>!) to remove the unit from our <code>$level-size</code> value:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Remove unit for calculations</span> <span class="token property"><span class="token variable">$level-unitless</span></span><span class="token punctuation">:</span> <span class="token variable">$level-size</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token variable">$level-size</span> <span class="token operator">*</span> 0 <span class="token operator">+</span> 1<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Next, we need to compute the minimum size that's acceptable for the font to shrink to.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Set minimum size to a percentage less than $level-size</span> <span class="token comment">// Reduction is greater for large font sizes (> 4rem) to help</span> <span class="token comment">// prevent overflow due to font-size on mobile devices</span> <span class="token property"><span class="token variable">$fluid-reduction</span></span><span class="token punctuation">:</span> <span class="token function">if</span><span class="token punctuation">(</span><span class="token variable">$level-size</span> <span class="token operator">></span> 4<span class="token punctuation">,</span> 0.5<span class="token punctuation">,</span> 0.33<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> <span class="token variable">$level-unitless</span> <span class="token operator">-</span> <span class="token punctuation">(</span><span class="token variable">$fluid-reduction</span> <span class="token operator">*</span> <span class="token variable">$level-unitless</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>You can adjust the if/else values for the <code>$fluid-reduction</code> variable to your taste, but this defines that for <code>$level-size</code> greater than <code>4rem</code>, we'll allow a reduction of <code>0.5</code> (50%) - and smaller sizes are allowed a <code>0.33</code> (33%) reduction.</p> <p>In pseudo-math, here's what's happening for the <code>h4</code> using the <code>perfectFourth</code> ratio:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> 1.33rem <span class="token operator">-</span> <span class="token punctuation">(</span>33% of 1.33<span class="token punctuation">)</span> = 0.89311<span class="token punctuation">;</span></code></pre> <p>The result is a 33% allowed reduction from the base <code>$level-size</code> value.</p> <p>The pseudo-math actually exposes an issue: this means that the <code>h4</code> <em>could</em> shrink below the <code>$type-base-size</code> (reminder: this is the base <code>body</code> font size).</p> <p>Let's add one more guardrail to prevent this issue. We'll double-check the result of <code>$fluid-min</code> and if it's going to be below <code>1</code> - the unitless form of <code>1rem</code> - we just set it to <code>1</code> (adjust this value if you have a different <code>$type-base-size</code>):</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Prevent dropping lower than 1rem (body font-size)</span> <span class="token property"><span class="token variable">$fluid-min</span></span><span class="token punctuation">:</span> <span class="token function">if</span><span class="token punctuation">(</span><span class="token variable">$fluid-min</span> <span class="token operator">></span> 1<span class="token punctuation">,</span> <span class="token variable">$fluid-min</span><span class="token punctuation">,</span> 1<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>We're missing one value which I have taken to calling the &quot;scaler&quot; - as in, the value that causes the fluid scaling to occur. It needs to be a value that by it's nature will change in order to trigger the transition between our min and max values.</p> <p>So, we'll be incorporating viewport units - specifically <code>vw</code>, or &quot;viewport width&quot;. When the viewport width changes, then this value will also update it's computed value. When it approaches our minimum value, it won't shrink further, and the true in the opposite direction for our max value. This creates the &quot;fluid&quot; type sizing effect.</p> <p>In order to retain accessible sizing via zooming, we'll also add <code>1rem</code> alongside our <code>vw</code> value. This helps alleviate (but not entirely rule out) side effects of using viewport units only. This is because as was mentioned earlier, the <code>rem</code> unit will scale with the user's zoom level as set via either their operating system <em>or</em> with in-browser zoom. To meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html">WCAG Success Criterion 1.4.4: Resize text</a>, a user must be able to increase font-size by up to 200%.</p> <p>Let's create our scaler value:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$fluid-scaler</span></span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$level-unitless</span> <span class="token operator">-</span> <span class="token variable">$fluid-min</span><span class="token punctuation">)</span> <span class="token operator">+</span> 4vw<span class="token punctuation">;</span></code></pre> <p>The logic applied here is to get the difference between the upper and lower limit, and add that value to a viewport unit of choice, in this case <code>4vw</code>. A value of 4 or 5 seems to be common in fluid typography recommendations, and testing against the <code>$type-ratios</code> seemed to surface <code>4vw</code> as keeping the most definition between heading levels throughout scaling. Please get in touch if you have a more formulaic way to arrive at the viewport value!</p> <p>Altogether, our fluid type <code>font-size</code> rule becomes:</p> <!-- prettier-ignore --> <pre class="language-scss"><code class="language-scss"><span class="token property">font-size</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span> <span class="token variable">#{$fluid-min}</span>rem<span class="token punctuation">,</span> <span class="token variable">#{$fluid-scaler}</span> <span class="token operator">+</span> 1rem<span class="token punctuation">,</span> <span class="token variable">#{$level-size}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <div class="heading-wrapper h2"> <h2 id="in-closing">In Closing...</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#in-closing" aria-labelledby="in-closing"><span hidden="">#</span></a></div> <p>If you really read this whole episode, thank you <em>so much</em> for sticking with it. Typography has so many angles and the &quot;right way&quot; is very project-dependent. It may be the set of properties with the most stakeholders and the most impact on any given layout.</p> <blockquote> <p>Consider reviewing the upgraded solution that drops Sass to use custom properties, and uses <a href="https://moderncss.dev/container-query-units-and-fluid-typography/">container query units for fluid typography</a> that flows easily into multiple layout locations.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/generating-font-size-css-rules-and-creating-a-fluid-type-scale/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>The demo includes all things discussed, and an extra bit of functionality which is that a map is created under the variable <code>$type-styles</code> to hold each generated value with <code>h[x]</code> as the key.</p> <p>Following the loop is the creation of the <code>type-style()</code> function that can retrieve values from the map based on a key such as <code>h3</code>. This can be useful for things like design systems where you may want to reference the <code>h3</code> font-size on the component level for visual consistency when perhaps a heading is semantically incorrect.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNOgeYv" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Resource: The Complete Guide to Centering in CSS</title>
<link href="https://moderncss.dev/resource-the-complete-guide-to-centering-in-css/"/>
<updated>2020-05-17T00:00:00Z</updated>
<id>https://moderncss.dev/resource-the-complete-guide-to-centering-in-css/</id>
<content type="html"><p>How could I write a series called &quot;Modern CSS Solutions <em>to old CSS problems</em>&quot; without covering the classic question:</p> <blockquote> <p>&quot;How do I center a div?&quot;</p> </blockquote> <p>Well, this new resource has you covered! We'll look at 3 categories:</p> <ul> <li>Vertically and Horizontally (XY)</li> <li>Vertical (Y)</li> <li>Horizontal (X)</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Each category shows solutions that explore using grid, flexbox, and block element layout.</p> <p>Check out <a href="https://moderncss.dev/complete-guide-to-centering-in-css/">the full guide</a> or jump to one of the following sections:</p> <ol> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#vertically-and-horizontally-xy">Vertically and Horizontally (XY)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-grid-solution">XY Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-flexbox-solution">XY Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-alternative-flexbox-solution">XY Alternative Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#xy-centering-for-block-elements">XY Centering for Block Elements</a></li> </ul> </li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#vertical-centering-y">Vertical Centering (Y)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-grid-solution">Y Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-flexbox-solution">Y Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#y-centering-for-block-elements">Y Centering for Block Elements</a></li> </ul> </li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#horizontal-centering-x">Horizontal Centering (X)</a> <ul> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-grid-solution">X Grid Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-flexbox-solution">X Flexbox Solution</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-centering-for-block-elements">X Centering for Block Elements</a></li> <li><a href="https://moderncss.dev/complete-guide-to-centering-in-css/#x-centering-for-dynamically-positioned-elements">X Centering for Dynamically Positioned Elements</a></li> </ul> </li> </ol> </content>
</entry>
<entry>
<title>Icon Button CSS Styling Guide</title>
<link href="https://moderncss.dev/icon-button-css-styling-guide/"/>
<updated>2020-05-13T00:00:00Z</updated>
<id>https://moderncss.dev/icon-button-css-styling-guide/</id>
<content type="html"><p>This guide will build on the previous episode <a href="https://moderncss.dev/css-button-styling-guide/">&quot;CSS Button Styling Guide&quot;</a> to explore the use case of icon buttons. We'll cover icon + text as well as icon-only buttons.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <blockquote> <p><strong>Note</strong>: With SVG now having excellent support, the preferred practice is to use it for icon systems vs. icon fonts. We will not dive into SVGs specifically, but we will assume SVG icons are in use.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="icon-text-button">Icon + Text Button</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-text-button" aria-labelledby="icon-text-button"><span hidden="">#</span></a></div> <p>First, let's do the easier extend from our current buttons, and drop an svg icon next to the text:</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>button__icon<span class="token punctuation">"</span></span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> Button Link <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>There are 3 key features about the SVG for the icon + text use case:</p> <ol> <li>Use of a new <code>button__icon</code> class</li> <li>The <code>viewBox</code> value is tight to the icon boundaries, ideally a square for best results across the icon set even if the values have variance (ex. <code>24</code> vs. <code>32</code>)</li> <li>For accessibility, we apply:</li> </ol> <ul> <li><code>aria-hidden=&quot;true&quot;</code> - allows assistive tech to skip the SVG since it's decorative and not providing any semantic value not already provided by the visible button text</li> <li><code>focusable=&quot;false&quot;</code> - prevents a &quot;double focus&quot; event in some version of IE</li> </ul> <blockquote> <p><strong>For more on accessibility of icon buttons</strong>: Read <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">this excellent article</a> by Sara Soueidan who is an expert on both accessibility and SVGs</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="icon-styling-for-icon-text">Icon Styling for Icon + Text</h3> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-styling-for-icon-text" aria-labelledby="icon-styling-for-icon-text"><span hidden="">#</span></a></div> <p>Due to <code>display: inline-flex</code> applied on the base <code>.button</code>, and no <code>width</code> attribute on the SVG, by default the icon is not yet visible.</p> <p>So let's first add dimensions to our new <code>.button__icon</code> class, using the <code>em</code> unit to keep it relative to the <code>font-size</code> in use:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// You may wish to have your icons appear larger</span> <span class="token comment">// or smaller in relation to the text</span> <span class="token property">width</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 0.9em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/00g7uw9dfcb80pq2hikz.png" alt="button icon with dimensions" /></p> <p>According to <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill#path">the spec default</a>, SVG parts including <code>path</code> have a <code>fill</code> of black. To adjust this, we will use the special keyword <code>currentColor</code> which will extend the button's applied text <code>color</code> to the SVG:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">fill</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0rs7lk1bmq6hqkcggekq.png" alt="button icon with currentColor as fill" /></p> <p>The last thing to adjust is to add a bit of spacing between the icon and button text, which we will again apply using the <code>em</code> unit:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/niqz77ol4aaskwjic6dw.png" alt="button icon with spacing applied" /></p> <p>We need to add one utility class to allow the icon to be placed after the text, or at the &quot;end&quot; of the button (for right-to-left languages). We zero out the existing margin, and flip it to the left:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button__icon </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>--end </span><span class="token punctuation">{</span> <span class="token property">margin-right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> Button Link <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>button__icon button__icon--end<span class="token punctuation">"</span></span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/xj30apl4rbcnzs1vjs8r.png" alt="icon placed at the end of the button" /></p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="icon-only-buttons">Icon-Only Buttons</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#icon-only-buttons" aria-labelledby="icon-only-buttons"><span hidden="">#</span></a></div> <p>We're going to make the assumption that we want both regular buttons (with or without icons) in addition to icon-only buttons. This is important because we will reuse the <code>.button</code> class in addition to a new class so that we don't have to redefine the resets and base visual styles. The overrides are minimal.</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button icon-button<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>Icon-only Button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 32 32<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<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>icon-button__icon<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span> <span class="token attr-name">focusable</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 punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M32 12.408l-11.056-1.607-4.944-10.018-4.944 10.018-11.056 1.607 8 7.798-1.889 11.011 9.889-5.199 9.889 5.199-1.889-11.011 8-7.798z<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>path</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <p>Changes from the icon + text button:</p> <ol> <li>Addition of the <code>icon-button</code> class to the <code>a</code></li> <li>Addition of <code>aria-label=&quot;Icon-only Button&quot;</code> to provide an accessible text alternative since we have removed the visual text</li> <li>Swap of the class on the SVG to <code>icon-button__icon</code></li> </ol> <blockquote> <p><strong>Important</strong>: the value of the <code>aria-label</code> should describe <em>what the button does</em> <strong>not</strong> <em>what the icon is</em>. For further reading and other ways to provide a text alternative, see <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/">Sara Soueidan's article</a></p> </blockquote> <p>Here's what we get before adjustments - an empty-looking button because we're back to the no-width problem:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/09pnf9xm2pectdy9ug6j.png" alt="pre-styled icon button" /></p> <p>First, let's create our new class. Due to <a href="https://dev.to/5t3ph/intro-to-the-css-cascade-the-c-in-css-1kh0">the &quot;C&quot; in CSS</a>, this rule needs to be placed after the <code>.button</code> rule:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.icon-button </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 2.5rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 2.5rem<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.35em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>__icon </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">fill</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We define a new <code>width</code> and <code>height</code> which is completely adjustable based on your design requirements, but it should equate to a square. This allows creation of a &quot;circle&quot; appearance when <code>border-radius: 50%</code> is applied.</p> <p>Then, we add a touch of padding (again to your tastes/design requirements) to add some breathing room between the SVG icon and the button boundary.</p> <p>Next, we define our <code>icon-button__icon</code> class. The difference here is that we want the <code>width</code> and <code>height</code> to match that of the container, so we set this to <code>100%</code>. This allows extending to multiple size icon-only buttons by only redefining the <code>font-size</code> property on the <code>.icon-button</code> class.</p> <p>Here's the progress:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/tdnn9ug4rcpmn45czqb1.png" alt="icon-only button styles" /></p> <p>It's not quite what we want, but we can fix it by adjusting the following properties within the <code>.button</code> class. We'll use the <code>:not()</code> selector to exclude the properties meant only for regular buttons:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.button </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token comment">// Find these styles and update, not duplicate:</span> &amp;<span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span>.icon-button<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 44px<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.75em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Now we have what we're after:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/aqknn8adugm9vn63c091.png" alt="completed icon-only button" /></p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/icon-button-css-styling-guide/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p>Includes use of the <code>.button--small</code> class created in the previous episode, as well as a &quot;real button&quot; to validate that styles work just as well for both elements:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="ExVpVJa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p><strong><a href="https://buttonbuddy.dev/">Try out ButtonBuddy to create accessible button colors</a></strong>. This web app I created will help get all the vectors of contrast right across your button color palette.</p> </blockquote> </content>
</entry>
<entry>
<title>CSS Button Styling Guide</title>
<link href="https://moderncss.dev/css-button-styling-guide/"/>
<updated>2020-05-07T00:00:00Z</updated>
<id>https://moderncss.dev/css-button-styling-guide/</id>
<content type="html"><p>This guide will explore the ins and outs of styling an accessible, extensible button appearance for both link and button elements.</p> <p>Topics covered include:</p> <ul> <li>reset styles for <code>a</code> and <code>button</code></li> <li>display, visual, size, and text styles</li> <li>accessible styling considerations</li> <li>extended styles for common scenarios</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Oh, the button (or is it a link?). I've battled the button since the days of hover delay from waiting for a second image to load, through image sprites, and then was immensely relieved when <code>border-radius</code>, <code>box-shadow</code> and gradients arrived on the scene.</p> <p>But... we took button styling too far, and somewhere along the way completely lost sight of what it really means to be a button, let alone an accessible button (or link).</p> <blockquote> <p><strong>STOP!</strong> Go read this excellent article: <a href="https://marcysutton.com/links-vs-buttons-in-modern-web-applications">Links vs. Buttons in Modern Web Applications</a> to understand when it's appropriate to use <code>a</code> versus <code>button</code></p> </blockquote> <p>We'll look at what properties are required to visually create a button appearance for both <code>a</code> and <code>button</code>, and additional details required to ensure they are created and used accessibly.</p> <div class="heading-wrapper h2"> <h2 id="reset-default-styles">Reset Default Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#reset-default-styles" aria-labelledby="reset-default-styles"><span hidden="">#</span></a></div> <p>Here's our baseline - native browser styles as rendered in Chrome, with the only changes so far being the link is inheriting the custom font set on the body, and I've bumped the <code>font-size</code> as well:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/1b5duijnf8zdydz1ue1p.png" alt="default link and button styles" /></p> <p>The HTML if you're playing along at home is:</p> <pre class="language-html"><code class="language-html"><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>javascript:;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Button Link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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 punctuation">></span></span>Real Button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <p>I've used the <code>javascript:;</code> string for the <code>href</code> value so that we could test states without triggering navigation. Similarly, since this button is not for a form submit, it needs the explicit type of <code>button</code> to prevent triggering a get request and page reload.</p> <div class="heading-wrapper h3"> <h3 id="reset-styles">Reset Styles</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#reset-styles" aria-labelledby="reset-styles"><span hidden="">#</span></a></div> <blockquote> <p><strong>Note</strong>: Typically I apply the <em>Normalize</em> reset to CodePens, but for this lesson we are starting from scratch to learn what is required to reset for buttons and links. Use of <em>Normalize</em> or other popular resets do some of these things for you.</p> </blockquote> <p>First, we'll add the class of <code>button</code> to both the link and the button just to emphasize where styles are being applied for this lesson.</p> <pre class="language-html"><code class="language-html"><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>javascript:;<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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Button Link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <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">class</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 punctuation">></span></span>Real Button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre> <h4><code>box-sizing</code></h4> <p>Ensure your styles include the following reset - if you don't want it globally (you should) you can scope it to our button class.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">* </span><span class="token punctuation">{</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>In a nutshell, this rule prevent things like borders and padding from expanding the expected element size (ex. a 25% width remains 25%, not 25% + border width + padding).</p> <h4><code>a</code></h4> <p>For the link, we only have one reset to do:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button </span><span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This simply removes the underline.</p> <h4><code>button</code></h4> <p>Next, we have a few more rules required to reset the button:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">button.button </span><span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token operator">and</span> <span class="token punctuation">(</span><span class="token property">-ms-high-contrast</span><span class="token punctuation">:</span> active<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid currentcolor<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>There are some differences in the <code>display</code> value as well between browsers, but we're going to change it to a unique option shortly.</p> <p>With these reset styles, we now have this appearance:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/44rie4nuqfk5jwpkk6ff.png" alt="link and button with reset styles" /></p> <p><em>Thanks to <a href="https://twitter.com/overflowhidden/status/1260837671762571265">@overflowhidden</a> for providing a solution to ensure a perceivable button border for users with Windows High Contrast mode enabled</em>.</p> <div class="heading-wrapper h2"> <h2 id="display-styles">Display Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#display-styles" aria-labelledby="display-styles"><span hidden="">#</span></a></div> <p>What I have found to work best across many scenarios is <code>display: inline-flex</code> which gives us the content alignment power of flexbox but sits in the DOM within <code>inline-block</code> behavior.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-flex<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Flex alignment comes in handy should you add icons in the future, or impose width restrictions.</p> <div class="heading-wrapper h2"> <h2 id="visual-styles">Visual Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#visual-styles" aria-labelledby="visual-styles"><span hidden="">#</span></a></div> <p>Next we'll apply some standard visual styles which you can certainly adjust to your taste. This is the most flexible group of styles and you can leave out <code>box-shadow</code> and/or <code>border-radius</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$btnColor</span></span><span class="token punctuation">:</span> #3e68ff<span class="token punctuation">;</span> <span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token variable">$btnColor</span><span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 3px 5px <span class="token function">rgba</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.18<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Now our link and button are starting to look more alike:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ctowxoi9kaub1bm96bz8.png" alt="link and button with visual styles" /></p> <div class="heading-wrapper h3"> <h3 id="button-contrast">Button Contrast</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#button-contrast" aria-labelledby="button-contrast"><span hidden="">#</span></a></div> <p>There are two levels of contrast involved when creating initial button styles:</p> <ol> <li>At least 3:1 between the button background color, and the background it is displayed against</li> <li>At least 4.5:1 (for text less than 18.66px bold or 24px) or 3:1 (for text greater than those measures) between the button text and the button background</li> </ol> <p>Here's an infographic I created to demonstrate how the button colors relate to their contrast relationships:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/58an5ksgtys6vhhq1tb4.png" alt="An infographic showing a &quot;default&quot; button that is a midrange shade of purple with white letters next to it's &quot;focus&quot; state which is a darker purple. Icons and labels show that the contrast of the default purple to the page background (a light yellow) is 4.17, and contrast of the default purple to the white button text is 4.5. For the focus button, there is a 3.02 contrast between the default purple background and the focus purple background, and 13.62 between focus purple and the white button text, and 12.57 between the focus purple and the page background light yellow." /></p> <p>Assuming a white page background, our button color choice passes with 4.54:1.</p> <blockquote> <p><strong><a href="https://buttonbuddy.dev/">Try out ButtonBuddy to create accessible button colors</a></strong>. This web app I created will help get all the vectors of contrast right across your button color palette.</p> </blockquote> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="size">Size</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#size" aria-labelledby="size"><span hidden="">#</span></a></div> <p>We intentionally left out one property under the &quot;Visual&quot; categorization that you might have missed upon seeing the progress screenshot: <code>padding</code>.</p> <p>Since <code>padding</code> is part of the <code>box-model</code>, we left it for the size section.</p> <p>Let's apply the size values and then discuss:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.75em<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 10ch<span class="token punctuation">;</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 44px<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We apply <code>padding</code> using <code>em</code> units, which is a preference that allows the padding to proportionally resize with the applied <code>font-size</code>.</p> <p>Next, we set a <code>min-width</code> using the <code>ch</code> unit, which is roughly equal to the width of the <code>0</code> character of the applied font and <code>font-size</code>. This recommendation is a visual rhythm guardrail. Consider the scenario you have two side-by-side buttons with one short and one longer label, ex. &quot;Share&quot; and &quot;Learn More&quot;. Without <code>min-width</code>, the &quot;Share&quot; button would be abruptly shorter than &quot;Learn More&quot;.</p> <p>The <code>min-height</code> is based on ensuring the button is a large enough target on touch devices to meet the WCAG 2.1 success criteria for <a href="https://www.w3.org/WAI/WCAG21/Understanding/target-size.html">2.5.5 - Target Size</a>.</p> <p>The styles are starting to come together, but we're not done yet:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/uylx2jbc92mr1bbdksoo.png" alt="link and button with size styles" /></p> <div class="heading-wrapper h2"> <h2 id="text-styles">Text Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#text-styles" aria-labelledby="text-styles"><span hidden="">#</span></a></div> <p>Based on the last progress screenshot, you might be tempted to skip text styles.</p> <p>But look what happens when we reduce the viewport size and trigger responsive behavior:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/s6rmhllldvvrw5wsgzhy.png" alt="link and button within reduced viewport" /></p> <p>As you can see, we have different alignment and the <code>line-height</code> could be adjusted as well.</p> <p>I intentionally skipped fixing text alignment in the reset styles, so we'll now make sure it's centered for both. Then we can also reduce the line-height - this may need adjusted depending on the font in use.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.1<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Alright, looking great!</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4ub4z1jxoxk3cp8gzg4a.png" alt="link and button with text styles" /></p> <div class="heading-wrapper h2"> <h2 id="state-styles">State Styles</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#state-styles" aria-labelledby="state-styles"><span hidden="">#</span></a></div> <p>Right now, the only visual feedback a user receives when attempting to interact with the buttons is the cursor changing to the &quot;pointer&quot; variation.</p> <p>There are three states we need to ensure are present.</p> <div class="heading-wrapper h3"> <h3 id="hover"><code>:hover</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#hover" aria-labelledby="hover"><span hidden="">#</span></a></div> <p>The one that usually gets the most attention is <code>hover</code>, so we'll start there.</p> <p>A typical update on hover is changing the background color. Since we were fairly close to 4.5, we will want to darken the color.</p> <p>We can take advantage of Sass to compute this color for us using the <code>$btnColor</code> variable we defined in the &quot;Visual&quot; section:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>:hover </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -20%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>The effect is a little jarring, but we have another modern CSS tool to soften this, aptly named <code>transition</code>. The <code>transition</code> property will need to be added outside of the <code>hover</code> rule so that it applies both on &quot;over&quot; and &quot;out&quot;.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transition</span><span class="token punctuation">:</span> 220ms all ease-in-out<span class="token punctuation">;</span> <span class="token comment">// ...&amp;:hover</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/0416wpo396xq4tbc5ln2.gif" alt="demo of hover transition" /></p> <div class="heading-wrapper h3"> <h3 id="focus"><code>:focus</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#focus" aria-labelledby="focus"><span hidden="">#</span></a></div> <p>For keyboard users, we need to ensure that the <code>focus</code> state is clearly distinguishable.</p> <p>By default, the browsers apply a sort of &quot;halo&quot; effect to elements that gain focus. A bad practice is simply removing the <code>outline</code> property which renders that effect and failing to replace it.</p> <p>We will replace the outline with a custom focus state that uses <code>box-shadow</code>. Like <code>outline</code>, <code>box-shadow</code> will not change the overall element size so it will not cause layout shifts. And, since we already applied a <code>transition</code>, the <code>box-shadow</code> will inherit that for use as well for an extra attention-getting effect.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">a.button, button.button </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token comment">// ...&amp;:hover</span> <span class="token selector"><span class="token parent important">&amp;</span>:focus </span><span class="token punctuation">{</span> <span class="token property">outline-style</span><span class="token punctuation">:</span> solid<span class="token punctuation">;</span> <span class="token property">outline-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0 0 4px <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -40%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Once again, we have used the <code>scale-color</code> function, this time to go even a bit darker than the <code>hover</code> color. This is because a button can be in both the <code>hover</code> and <code>focus</code> states at the same time.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/03cq806lheglhyn5xf6l.gif" alt="demo of link and button focus" /></p> <p><em>Thanks to <a href="https://twitter.com/overflowhidden/status/1260837671762571265">@overflowhidden</a> for providing a solution to ensure a perceivable <code>:focus</code> state for users with Windows High Contrast mode enabled</em>.</p> <div class="heading-wrapper h3"> <h3 id="active"><code>:active</code></h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#active" aria-labelledby="active"><span hidden="">#</span></a></div> <p>Lastly, particularly for the &quot;real button&quot;, it is best to define an <code>:active</code> state style.</p> <p>For links this appears for a brief moment during the &quot;down&quot; of a click/tap.</p> <p>For buttons, this can be shown for a longer duration given that a button can be triggered with the space key which can be held down indefinitely.</p> <p>We will append <code>:active</code> to our existing <code>:hover</code> style:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>:hover, <span class="token parent important">&amp;</span>:active </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">scale-color</span><span class="token punctuation">(</span><span class="token variable">$btnColor</span><span class="token punctuation">,</span> <span class="token property"><span class="token variable">$lightness</span></span><span class="token punctuation">:</span> -20%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="style-variations">Style Variations</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#style-variations" aria-labelledby="style-variations"><span hidden="">#</span></a></div> <p>The topic of outlined (&quot;ghost&quot;) buttons is a topic for a different day, but there are two variations that we'll quickly add.</p> <div class="heading-wrapper h3"> <h3 id="small-buttons">Small Buttons</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#small-buttons" aria-labelledby="small-buttons"><span hidden="">#</span></a></div> <p>Using BEM format, we'll create the <code>button--small</code> class to simply reduce font size. Since we set padding to <code>em</code>, that will proportionately resize. And our <code>min-height</code> will ensure the button remains a large enough touch target.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>--small </span><span class="token punctuation">{</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.15rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="block-buttons">Block Buttons</h3> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#block-buttons" aria-labelledby="block-buttons"><span hidden="">#</span></a></div> <p>There may be times you do want <code>block</code> behavior instead of inline, so we'll add <code>width: 100%</code> to allow for that option instead of changing the <code>display</code> prop since we still want flex alignment on the button contents:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector"><span class="token parent important">&amp;</span>--block </span><span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="gotcha-child-of-flex-columns">Gotcha: Child of Flex Columns</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#gotcha-child-of-flex-columns" aria-labelledby="gotcha-child-of-flex-columns"><span hidden="">#</span></a></div> <p>Given the scenario the button is a child of a flex column, you may be caught off guard when the button expands to full-width even without the <code>button--block</code> class.</p> <p>To future-proof against this scenario, you can add <code>align-self: start</code> to the base button styles, or create utility styles for each of the flex/grid alignment property values: <code>start</code>, <code>center</code>, and <code>end</code>.</p> <div class="heading-wrapper h2"> <h2 id="demo">Demo</h2> <a class="anchor" href="https://moderncss.dev/css-button-styling-guide/#demo" aria-labelledby="demo"><span hidden="">#</span></a></div> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="rNOpgPa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Solutions to Replace the 12-Column Grid</title>
<link href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/"/>
<updated>2020-05-03T00:00:00Z</updated>
<id>https://moderncss.dev/solutions-to-replace-the-12-column-grid/</id>
<content type="html"><p>Let's create simplified responsive grid systems using both CSS grid and flexbox and ditch the bulk of 12-column grid systems from heavy frameworks.</p> <p>If you haven't really looked into grid, or rely on frameworks to think about flexbox for you, this will help you level up your understanding 🚀</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>Looking across the web, you will often see content laid out in a few select flavors:</p> <ul> <li>fullwidth of its container</li> <li>two equal-width columns</li> <li>three equal-width columns</li> <li>four equal-width columns</li> </ul> <p>Usually, this is accomplished by a considerable amount of utility classes setting widths across breakpoints.</p> <p>Between CSS grid and flexbox, and with the aforementioned layouts in mind, we can greatly reduce the setup of responsive grid columns.</p> <p>For both solutions, we will create just two classes and be able to handle from 1-4 columns of content that responsively resizes equally 🙌</p> <blockquote> <p><strong>Note</strong>: These solutions as-is work best for defining primary page layout containers, but we'll end with some suggestions on filling the gap for other layout alignment needs.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="the-grid-solution">The Grid Solution</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#the-grid-solution" aria-labelledby="the-grid-solution"><span hidden="">#</span></a></div> <p>Grid excels at grids, as the name would imply. Here, the terms &quot;column&quot; and &quot;row&quot; are inherent to the way you work with CSS grid, which can make defining your solution more clear.</p> <p>In particular are the following useful features:</p> <ul> <li><code>gap</code> - defines equal space between grid items, whether columns or rows</li> <li><code>repeat()</code> - quickly define rules for every row or column, or a set number of rows or columns</li> <li><code>fr</code> unit - the available &quot;fraction&quot; of space that is left to distribute to that column or row</li> <li><code>minmax()</code> - define a minimum and maximum accepted column width or row height</li> </ul> <div class="heading-wrapper h3"> <h3 id="grid-wrap"><code>.grid-wrap</code></h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid-wrap" aria-labelledby="grid-wrap"><span hidden="">#</span></a></div> <p>First, we create a wrapping class. This is only to apply the equivalent of our <code>gap</code> value as padding and is totally optional. You may want this because the <code>gap</code> property does not apply the gap spacing to the outside of the grid. Perhaps padding is already applied to your containing element which may be the <code>body</code>, or you may actually want your grid columns to touch edge-to-edge of the viewport.</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$gridGap</span></span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector">.grid-wrap </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="grid"><code>.grid</code></h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#grid" aria-labelledby="grid"><span hidden="">#</span></a></div> <p>This is it - the one class that can quickly turn any element into a grid container where it's immediate children then become equal-width, responsive columns.</p> <p>Here's the full rule, and then we'll break it down:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$minColWidth</span></span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span><span class="token variable">$minColWidth</span><span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> + .grid </span><span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>First, we define a minimum width for our content columns. I recommend using <code>rem</code> for this value so that it is consistent throughout your experience. If we set it based on <code>em</code> it would be altered with any change in base element font-size. <a href="https://dev.to/5t3ph/guide-to-css-units-for-relational-spacing-1mj5">Learn more about working with units &gt;</a></p> <p>Then, the magic comes from how we define <code>grid-template-columns</code>.</p> <p>We use the <code>repeat</code> function to say that we want the same parameters applied across all columns that exist.</p> <p>Then, instead of an absolute number, we use the <code>auto-fit</code> value which is responsible for ensuring the columns stay equal-width by stretching columns to fill any available space.</p> <p>After that, we use <code>minmax()</code> to set the minimum allowed column width, and then use <code>1fr</code> as the max which ensures the content fills the column as much as room allows.</p> <p>Then we add our gap, and an optional rule to apply the same value between consecutive <code>.grid</code> containers.</p> <p>Here's the solution altogether:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvrZVx" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <p><em>Note</em>: You could technically add many more than 4 columns within <code>.grid</code>, they will just become more narrow up until the minimum width even on larger viewports.</p> <blockquote> <p>Check out my <a href="https://egghead.io/lessons/css-create-a-basic-responsive-grid-system-with-css-grid">egghead video lesson</a> on how this technique comes together.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="drawbacks">Drawbacks</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#drawbacks" aria-labelledby="drawbacks"><span hidden="">#</span></a></div> <p>In the case of a 3-column + grid, while it does respond nicely, you will end up with an &quot;orphan&quot; column on some viewport widths.</p> <p>You can overcome this with media queries, but they will be brittle.</p> <p>If it is essential to the design to prevent orphan columns, you may want to opt for the flexbox solution instead.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-solution">Flexbox Solution</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flexbox-solution" aria-labelledby="flexbox-solution"><span hidden="">#</span></a></div> <p>Our flexbox solution will mimic grid in that the priority is equal-width columns.</p> <p>However, there is not yet a fully supported flexbox gap property (one is <a href="https://twitter.com/argyleink/status/1254794309263491072">on the way</a>!), so we have to do some trickery to accomplish the same effect.</p> <div class="heading-wrapper h2"> <h2 id="flex-grid-wrap"><code>.flex-grid-wrap</code></h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flex-grid-wrap" aria-labelledby="flex-grid-wrap"><span hidden="">#</span></a></div> <p>Same intention as the grid solution:</p> <pre class="language-scss"><code class="language-scss"><span class="token property"><span class="token variable">$gridGap</span></span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector">.flex-grid-wrap </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="flex-grid"><code>.flex-grid</code></h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#flex-grid" aria-labelledby="flex-grid"><span hidden="">#</span></a></div> <p>Inherent flexbox behavior places items in a row where each item grows with content length and as it grows it bumps the next item over.</p> <p>So, we must add a bit of extra logic to create equal-width behavior.</p> <p>We define the rule with <code>display: flex</code>, and then we add a rule that directs immediate children to use <code>flex</code> behavior that evaluates to:</p> <ul> <li><code>flex-grow: 0</code> - prevents growing beyond an equitably shared amount of space</li> <li><code>flex-shrink: 1</code> - directs elements to &quot;shrink&quot; at the same rate</li> <li><code>flex-basis: 100%</code> - counteracts the <code>flex-grow</code> directive to still expand items to fill available space</li> </ul> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex-grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> > * </span><span class="token punctuation">{</span> <span class="token property">flex</span><span class="token punctuation">:</span> 0 1 100%<span class="token punctuation">;</span> &amp;<span class="token punctuation">:</span><span class="token function">not</span><span class="token punctuation">(</span><span class="token punctuation">:</span>first-child<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>And to make up for no gap rule, we define <code>margin-left</code> on all but the first item.</p> <div class="heading-wrapper h2"> <h2 id="handle-for-small-viewports">Handle for small viewports</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#handle-for-small-viewports" aria-labelledby="handle-for-small-viewports"><span hidden="">#</span></a></div> <p>Great start, but this will never break down for small viewports:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/v9c82kc506hbcx09qcj9.png" alt="current flex column behavior on small viewport" /></p> <p>As noted at the start, since this grid solution is intended to be used for primary page layout containers, we will bring in media queries to insert a breakpoint by allowing for <code>flex-wrap: wrap</code>, and switching our margin &quot;gap hack&quot; to a top instead of left margin.</p> <p>To determine when to add wrapping, the baseline solution multiplies our minimal acceptable width by 3. The logic here is that once 3 columns individual widths are less than our acceptable minimum, we break and toss everything full-width instead. Depending on your acceptable minimum, you may alter this rule.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.flex-grid </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> @media <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$minColWidth</span> <span class="token operator">*</span> 3<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span> > * </span><span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 2rem 0 0 <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> @media <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token variable">$minColWidth</span> <span class="token operator">*</span> 3<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token selector"><span class="token parent important">&amp;</span> + .flex-grid </span><span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> <span class="token variable">$gridGap</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>We also added a <code>min-width</code> query so that we have the top margin &quot;gap&quot; on larger viewports. If we had it on small as well, we would end up with double the margin between groups of content, which is possibly a desirable outcome.</p> <p>Here's the flexbox solution demo:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="eYpeNxd" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h3"> <h3 id="drawbacks-1">Drawbacks</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#drawbacks-1" aria-labelledby="drawbacks-1"><span hidden="">#</span></a></div> <p>Applying this grid to sub-containers within your page may cause undesirable breakpoint issues since it's a manual media query that is looking at the viewport width and not the container width.</p> <p><strong>Possible remedy</strong>: Instead of always applying the <code>max-width</code> query, you may apply that with a class. That would enable using this base grid idea for sub-containers with less undesirable results.</p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which Is Better?</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>The solutions proposed are very general but have wide application.</p> <p>The intent of each is to be applied to direct children of the <code>body</code>, or one layer deep such as to a <code>main</code> component that limits overall <code>max-width</code> of the content spread but still responds downward in sync with the viewport.</p> <div class="heading-wrapper h3"> <h3 id="choose-grid-if">Choose Grid if:</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#choose-grid-if" aria-labelledby="choose-grid-if"><span hidden="">#</span></a></div> <ul> <li>you want to take advantage of <code>auto-fit</code> + <code>minmax</code> behavior to automatically bump items to a new row once the minimum acceptable width is hit</li> <li>you plan to use in sub-containers since media queries are not required to apply breakpoints (you could extend the idea to apply to components like navbars or card action items by setting a smaller min-width)</li> <li>you'd like to <em>almost</em> achieve container queries since items respond according to their content length</li> </ul> <div class="heading-wrapper h3"> <h3 id="choose-flexbox-if">Choose Flexbox if:</h3> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#choose-flexbox-if" aria-labelledby="choose-flexbox-if"><span hidden="">#</span></a></div> <ul> <li>the only place you need &quot;grid&quot; behavior is to layout primary page containers, such as to define rows of cards or create two-column text content</li> <li>you want to prevent &quot;orphan&quot; columns</li> </ul> <div class="heading-wrapper h2"> <h2 id="if-you-really-want-a-12-column-grid">If You <em>Really</em> Want A 12-Column Grid</h2> <a class="anchor" href="https://moderncss.dev/solutions-to-replace-the-12-column-grid/#if-you-really-want-a-12-column-grid" aria-labelledby="if-you-really-want-a-12-column-grid"><span hidden="">#</span></a></div> <p>Here it is - but you're responsible for placing items on it how you'd like which means more custom CSS rules :)</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>12<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Alternatively, create just a handful of targeted classes to more clearly define column expectations. Note that this type of usage means that columns will take up precisely the fraction of space that would equal 1/2, or 1/3, or 1/4. So if you have only one column in the <code>2cols</code> grid, it will still only span half the total width, not fill up available space.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.grid </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>--2cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>2<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>--3cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector"><span class="token parent important">&amp;</span>--4cols </span><span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>4<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <blockquote> <p>If you're interested in a light-weight starting place for a basic HTML/Sass solution that includes minimal, general application layout containers and utilities, check out my <a href="https://5t3ph.github.io/html-sass-jumpstart/">jumpstart &gt;</a></p> </blockquote> </content>
</entry>
<entry>
<title>CSS-Only Accessible Dropdown Navigation Menu</title>
<link href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/"/>
<updated>2020-04-23T00:00:00Z</updated>
<id>https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/</id>
<content type="html"><p>This technique explores using:</p> <ul> <li>Animation with CSS <code>transition</code> and <code>transform</code></li> <li>Using the <code>:focus-within</code> pseudo-class</li> <li>CSS grid for positioning</li> <li>dynamic centering technique</li> <li>Accessibility considerations for dropdown menus</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <p>If you've ever pulled your hair out dealing with the concept of &quot;hover intent&quot;, then this upgrade is for you!</p> <p>Before we get too far, while our technique 100% uses only CSS, there is a need to add some Javascript for a more comprehensively accessible experience. There is also a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a> needed for a key feature to make this work - <code>:focus-within</code> - <a href="https://caniuse.com/#search=focus-within">for the most reliable support</a>. But we've still greatly improved from the days of needing one or more jQuery plugins to accomplish the visual effects.</p> <blockquote> <p><strong>Accessibility update - 08/18/20</strong>: A huge thanks to <a href="https://twitter.com/mfairchild365">Michael Fairchild</a> of Deque (and creator of the excellent resource <a href="https://a11ysupport.io/">a11ysupport.io</a>) for testing the original solution across various assistive technology. The CSS-only method needs some Javascript to fully meet WCAG 2.1. In particular, javascript needs to be used to offer a non-mouse/non-tab way to dismiss the menu (think escape key) to meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html">success criteria 1.4.13</a>. Michael pointed to <a href="https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html">this WAI-ARIA Authoring Practices demo</a> which provides more info on the necessary Javascript features. These are highly recommended additions for your final production solution.</p> </blockquote> <hr /> <p>If you've not used Sass, you may want to take five minutes to understand <a href="https://sass-lang.com/guide#topic-3">the nesting syntax of Sass</a> to most easily understand the code samples given.</p> <div class="heading-wrapper h2"> <h2 id="base-navigation-html">Base Navigation HTML</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#base-navigation-html" aria-labelledby="base-navigation-html"><span hidden="">#</span></a></div> <p>We will enhance this as we continue, but here's our starting structure:</p> <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">aria-label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Main Navigation<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>About<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</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>dropdown<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- aria-expanded needs managed with Javascript --></span> <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">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>dropdown__title<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>sweets-dropdown<span class="token punctuation">"</span></span> <span class="token punctuation">></span></span> Sweets <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span> <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>dropdown__menu<span class="token punctuation">"</span></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>sweets-dropdown<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Donuts<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Cupcakes<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Chocolate<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Bonbons<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><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 punctuation">></span></span>Order<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">></span></span></code></pre> <p>Overlooking the <code>button</code> for a minute, this is the semantic standard for navigation links. This structure is flexible to live anywhere on your page, so it could be a table of contents in your sidebar as easily as it is the main navigation.</p> <p>Right out the gate, we have implemented a few features specifically for accessibility:</p> <ol> <li><code>aria-label</code> on the <code>&lt;nav&gt;</code> to help identify it's purpose when assistive tech is used to navigate a page by landmarks</li> <li>Use of a <code>button</code> as a focusable, discoverable element to trigger the opening of the dropdown</li> <li><code>aria-controls</code> on the <code>.dropdown__title</code> that links to the id of the <code>.dropdown__menu</code> to associate it with the menu for assistive tech</li> <li><code>aria-expanded</code> on the <code>button</code> which in your final solution needs toggled via Javascript as noted in the demo mentioned at the beginning of this article</li> </ol> <blockquote> <p>As noted by Michael, use of a <code>button</code> element also allows Dragon Naturally Speaking users to say something like 'click button' to open the menu.</p> </blockquote> <p>Our (mostly) default starting appearance is as follows:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ph6ne7veudghpvnajgp6.png" alt="default list of links" /></p> <div class="heading-wrapper h2"> <h2 id="initial-navigation-styles">Initial Navigation Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#initial-navigation-styles" aria-labelledby="initial-navigation-styles"><span hidden="">#</span></a></div> <p>First, we'll give some container styles to <code>nav</code> and define it as a grid container. Then we'll remove default list styles from the <code>nav ul</code> and <code>nav ul li</code>.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 1rem<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">place-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token selector">ul </span><span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token selector">li </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/awvgs6rxt2j1787b9lbi.png" alt="navigation list with list styles removed" /></p> <p>We've lost the hierarchical definition, but we can begin to bring it back with the following:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token comment">// ...existing styles</span> <span class="token selector">> ul </span><span class="token punctuation">{</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token selector">> li </span><span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>By using the child combinator selector <code>&gt;</code> we've defined that the top-level <code>ul</code> which is a direct child of <code>nav</code> should switch it's <code>grid-auto-flow</code> to <code>column</code> which effectively updates it to be along the <code>x-axis</code>. We then add margin to the top-level <code>li</code> elements for a bit more definition. Now, the future dropdown items are appearing contained below the &quot;Sweets&quot; menu and are more clearly its children:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/y6ikx5v9h84lm44cfksp.png" alt="nav list with direct child styles" /></p> <p>Next we'll add a touch of style first to all links as well as the <code>.dropdown__title</code>, then to only the top-level links in addition to the <code>.dropdown__title</code>. This is also where we clear out the native browser styles inherited for <code>button</code> elements.</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">// Clear native browser button styles</span> <span class="token selector">.dropdown__title </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">nav </span><span class="token punctuation">{</span> <span class="token selector">> ul </span><span class="token punctuation">{</span> <span class="token selector">> li </span><span class="token punctuation">{</span> <span class="token comment">// All links contained in the li</span> <span class="token selector">a, .dropdown__title </span><span class="token punctuation">{</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.125rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Only direct links contained in the li</span> <span class="token selector">> a, .dropdown__title </span><span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1rem 0.5rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/pmnd1jz05g8u934r72lk.png" alt="updated link styles" /></p> <div class="heading-wrapper h2"> <h2 id="base-dropdown-styles">Base Dropdown Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#base-dropdown-styles" aria-labelledby="base-dropdown-styles"><span hidden="">#</span></a></div> <p>We have thus far been relying on element selectors, but we will bring in class selectors for the dropdown since there may be multiple in a given navigation list.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>We'll first style up the <code>.dropdown__menu</code> and its links to help identify it more clearly as we work through positioning and animation:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 0.15em 0.25em <span class="token function">rgba</span><span class="token punctuation">(</span>black<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em 0<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 15ch<span class="token punctuation">;</span> <span class="token selector">a </span><span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #444<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8fgjgciuge2ts2k0nqbl.png" alt="dropdown__menu styles" /></p> <p>One of the clear issues is that the <code>.dropdown__menu</code> is affecting the <code>nav</code> container, which you can see from the grey <code>nav</code> background being present around the dropdown.</p> <p>We can start to fix this by adding <code>position: absolute</code> to the <code>.dropdown__menu</code> which takes it out of normal document flow:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jdvoh962drdgg0vu4bze.png" alt="menu with position absolute" /></p> <p>You can see it's aligned to the left and below of the parent list item. Depending on your design, this may be the desirable location.</p> <p>We're going to pull out a centering trick to align the menu central to the list item:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token comment">// Pull up to overlap the parent list item very slightly</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100% <span class="token operator">-</span> 0.25rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Use the left from absolute position to shift the left side</span> <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token comment">// Use translateX to shift the menu 50% of it's width back to the left</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The magic of this centering technique is that the menu could be any width or even a dynamic width and it would center appropriately.</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/rgd0anjvrugsq1x8mk9x.png" alt="centered dropdown__menu styles" /></p> <div class="heading-wrapper h2"> <h2 id="dropdown-reveal-styles">Dropdown Reveal Styles</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#dropdown-reveal-styles" aria-labelledby="dropdown-reveal-styles"><span hidden="">#</span></a></div> <p>There are two primary triggers we want used to open the menu: <code>:hover</code> and <code>:focus</code>.</p> <p>However, traditional <code>:focus</code> will not persist the open state of the dropdown. Once the initial trigger loses focus, the keyboard focus may still move through the dropdown menu, but visually the menu would disappear.</p> <div class="heading-wrapper h3"> <h3 id="focus-within"><code>:focus-within</code></h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#focus-within" aria-labelledby="focus-within"><span hidden="">#</span></a></div> <p>There is an upcoming pseudo-class called <code>:focus-within</code> and it is precisely what we need to make it possible for this to be a CSS-only dropdown. As mentioned in the intro, it does require a <a href="https://allyjs.io/api/style/focus-within.html">polyfill</a> if you need to support IE &lt; Edge 79 (<a href="https://caniuse.com/#search=focus-within">you do</a>... for now).</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within">From MDN</a>, italics mine to show the part we're going to benefit from:</p> <blockquote> <p>The <code>:focus-within</code> CSS pseudo-class represents an element that has received focus <em>or contains an element that has received focus</em>. In other words, it represents an element that is itself matched by the <code>:focus</code> pseudo-class <em>or has a descendant that is matched by <code>:focus</code></em>.</p> </blockquote> <div class="heading-wrapper h3"> <h3 id="hide-the-dropdown-by-default">Hide the dropdown by default</h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#hide-the-dropdown-by-default" aria-labelledby="hide-the-dropdown-by-default"><span hidden="">#</span></a></div> <p>Before we can reveal the dropdown, we need to hide it, so we will use the hidden styles as the default state.</p> <p>Your first instinct may be <code>display: none</code> but that locks us out of gracefully animating the transition.</p> <p>Next, you might try simply <code>opacity: 0</code> which visibly hides it but leaves behind &quot;ghost links&quot; because the element still has computed height.</p> <p>Instead, we will use a combination of <code>opacity</code>, <code>transform</code>, and <code>visibilty</code>:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotateX</span><span class="token punctuation">(</span>-90deg<span class="token punctuation">)</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> top center<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.3<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We add opacity but not all the way to 0 to enable a bit smoother effect later.</p> <p>And, we update our <code>transform</code> property to include <code>rotateX(-90deg)</code>, which will rotate the menu in 3D space to 90 degrees &quot;backwards&quot;. This effectively removes the height and will make for an interesting transition on reveal. Also you'll notice the <code>transform-origin</code> property which we add to update the point around which the transform is applied, versus the default of the horizontal and vertical center.</p> <p>Additionally, to meet <a href="https://www.w3.org/WAI/WCAG21/Understanding/meaningful-sequence.html">success criteria 1.3.2</a>, the links should be hidden from screen reader users until they are visually displayed. We ensure this behavior by including <code>visibility: hidden</code> (thanks again to Michael for this tip!).</p> <p>Before we do the reveal, we need to add a <code>transition</code> property. We add it to the main <code>.dropdown__menu</code> rule so that it applies both on and off focus/hover, aka &quot;forwards&quot; and &quot;backwards&quot;.</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token property">transition</span><span class="token punctuation">:</span> 280ms all ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h2"> <h2 id="revealing-the-dropdown">Revealing the dropdown</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#revealing-the-dropdown" aria-labelledby="revealing-the-dropdown"><span hidden="">#</span></a></div> <p>With all that prior setup, revealing the dropdown on both hover and focus can be accomplished as succinctly as:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector"><span class="token parent important">&amp;</span>:hover, <span class="token parent important">&amp;</span>:focus-within </span><span class="token punctuation">{</span> <span class="token selector">.dropdown__menu </span><span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotateX</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span> <span class="token function">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">visibility</span><span class="token punctuation">:</span> visible<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>First, we reverse the <code>visibilty</code> (or the other properties would not work), and then we've reversed the <code>rotateX</code> by resetting to 0, and then bring the <code>opacity</code> all the way up to <code>1</code> for full visibility.</p> <p>Here's the result:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5z5zaa54czp54u7jleca.gif" alt="demo of reveal on focus and hover" /></p> <p>The <code>rotateX</code> property allows the appearance of the menu swinging in from the back, and <code>opacity</code> just makes it a little softer transition overall.</p> <blockquote> <p>Once again a note that for full accessibility, there is a need for Javascript to fully handle for keyboard assistive tech events that do not always trigger <code>:focus</code>. This means some sighted keyboard users may discover the dropdown links, but without a <code>:focus</code> event emitted, they will not see the dropdown menu actually open. Review the <a href="https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html">w3c demo</a> for how to finish incorporating Javascript in this solution.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="handling-hover-intent">Handling Hover Intent</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#handling-hover-intent" aria-labelledby="handling-hover-intent"><span hidden="">#</span></a></div> <p>If you've been at this web thing for a while, I'm hoping the following will make you go 🤯</p> <p>When I first began battling dropdown menus I was creating them primarily for IE7. On a big project, several team members asked something along the lines of &quot;can you stop the menu appearing if I'm just scrolling/mousing over the menu?&quot;. The solution I finally found after much Googling (including trying to come up with the right phrase to get what I was after) was the <a href="https://briancherne.github.io/jquery-hoverIntent/">hoverIntent jQuery plugin</a>.</p> <p>I needed to set that up because since we are using the <code>transition</code> property, we can also add a very slight delay. For general purposes, this will prevent the dropdown animation triggering for &quot;drive-by&quot; mouseovers.</p> <p>Order matters when we're defining all transition properties in one line, and the second numerical value in order will be picked up as the delay value:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.dropdown__menu</span> <span class="token punctuation">{</span> // ... existing styles <span class="token property">transition</span><span class="token punctuation">:</span> 280ms all 120ms ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Check out the results:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/mmts717vg1uxyt8mivgq.gif" alt="demo of transition delay with mouseover" /></p> <p>It takes a pretty leisurely rollover to trigger the menu, which we can loosely infer as intent to open the menu. The delay is still short enough to not be consciously noticed prior to opening the menu, so it's a win!</p> <p>You may still choose to use Javascript to enhance this particularly if it's going to launch a &quot;mega menu&quot; that would be more disruptive, but this is still pretty delightful.</p> <div class="heading-wrapper h2"> <h2 id="dropdown-menu-indicator">Dropdown Menu Indicator</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#dropdown-menu-indicator" aria-labelledby="dropdown-menu-indicator"><span hidden="">#</span></a></div> <p>Hover intent is one thing, but really we need an additional cue to the user that this menu has additional options. An extremely common convention is a &quot;caret&quot; or &quot;down arrow&quot; mimicking the indicator of a native select element.</p> <p>To add this, we will update the <code>.dropdown__title</code> styles. We'll define it as an <code>inline-flex</code> container and then create an <code>:after</code> element that uses the border trick to create a downward arrow. We use a dash of <code>translateY()</code> to optically align it with our text:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">.dropdown </span><span class="token punctuation">{</span> <span class="token comment">// ... existing styles</span> <span class="token selector">.dropdown__title </span><span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-flex<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token selector"><span class="token parent important">&amp;</span>:after </span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 0.35rem solid transparent<span class="token punctuation">;</span> <span class="token property">border-top-color</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>blue<span class="token punctuation">,</span> 0.45<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">margin-left</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0.15em<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/t6fj8oode9wkn736dyq7.png" alt="dropdown caret indicator" /></p> <div class="heading-wrapper h3"> <h3 id="closing-the-menu-on-mobile">Closing the menu on mobile</h3> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#closing-the-menu-on-mobile" aria-labelledby="closing-the-menu-on-mobile"><span hidden="">#</span></a></div> <p>Here's another place where ultimately you may have to enhance with Javascript.</p> <p>To keep it CSS-only, and acceptable for non-application websites, you need to apply <code>tabindex=&quot;-1&quot;</code> on the body, effectively allowing any clicks outside of the menu to remove focus from it and allowing it to close.</p> <p>This is a bit of a stretch - and it may be a little frustrating to users - so you may want to enhance this to hide on scroll as well with Javascript especially if you define the <code>nav</code> to use <code>position: sticky</code> and scroll with the user.</p> <div class="heading-wrapper h2"> <h2 id="final-result">Final Result</h2> <a class="anchor" href="https://moderncss.dev/css-only-accessible-dropdown-navigation-menu/#final-result" aria-labelledby="final-result"><span hidden="">#</span></a></div> <p>Here's the final result with a bit of extra styling including an arrow to more visually connect the menu to the link item, custom focus states on all the nav links, and <code>position: sticky</code> on the <code>nav</code>:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="MWaJePa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> </content>
</entry>
<entry>
<title>Responsive Image Gallery With Animated Captions</title>
<link href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/"/>
<updated>2020-04-21T00:00:00Z</updated>
<id>https://moderncss.dev/responsive-image-gallery-with-animated-captions/</id>
<content type="html"><style>.demo .demo--content { background-color: transparent; padding: 1rem; }</style> <p>Responsively resizing images is a common need, and modern CSS provides tools for ensuring a consistent <code>aspect-ratio</code> while not distorting the images. And grid gives us flexibility for a gallery layout as well as positioning multiple elements in a shared space.</p> <p>This responsive gallery technique explores using:</p> <ul> <li><code>object-fit</code> for responsive image scaling</li> <li><code>aspect-ratio</code> for consistent image sizes</li> <li>A CSS Grid trick to replace absolute positioning</li> <li>CSS transforms for animated effects</li> <li>handling for touch devices</li> <li>respecting reduced motion</li> </ul> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="gallery-html">Gallery HTML</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#gallery-html" aria-labelledby="gallery-html"><span hidden="">#</span></a></div> <p>Here is our initial HTML, which is an <code>ul</code> where each <code>li</code> contains a <code>figure</code> with the image and <code>figcaption</code>:</p> <pre class="language-html"><code class="language-html"><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>gallery<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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/550/300<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Candy canes ice cream<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/400<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Ice cream biscuit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">alt</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">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://picsum.photos/600/450<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">></span></span>Cream biscuit marzipan<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <blockquote> <p>What's that <code>role=&quot;list&quot;</code> doing there? It <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">ensures assistive technology still interprets the element as a list</a> after we remove list styling with CSS.</p> </blockquote> <p>I've used different image sizes both to showcase how <code>object-fit</code> works in terms of fitting its container, and also to lessen the chance of duplicate images from the <a href="https://picsum.photos/">picsum</a> service.</p> <p>Note that due to using a random image service, I haven't provided full <code>alt</code> descriptions or real <code>figcaption</code> text for these demo images. Ideally you should write <code>alt</code> that describes the image, and use <code>figcaption</code> to provide context for the image as a figure. I recommend this resource to <a href="https://thoughtbot.com/blog/alt-vs-figcaption">learn more about the importance of writing <code>alt</code> and <code>figcaption</code></a>.</p> <div class="heading-wrapper h2"> <h2 id="base-gallery-styles">Base Gallery Styles</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#base-gallery-styles" aria-labelledby="base-gallery-styles"><span hidden="">#</span></a></div> <p>Since we've used a list, we need to remove default list styles, and we will also set the list up as a grid container. These initial styles achieve placing our list items in a row and ensures the images stay in their grid columns but does not resize them.</p> <details open=""> <summary>CSS for "gallery class"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fit<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>20ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-12 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-12 img { display: block; width: 100%; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-12" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="gallery-card-and-image-styles">Gallery Card and Image Styles</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#gallery-card-and-image-styles" aria-labelledby="gallery-card-and-image-styles"><span hidden="">#</span></a></div> <p>If you're like me and have tried to do this in years past, you probably threw your rollerball mouse across the room trying to figure out why <code>position: absolute</code> wasn't playing nicely with your jQuery animations.</p> <p>CSS Grid and CSS transforms are here to save the day! 🎉</p> <p>We're going to setup the <code>figure</code> to use grid display, and also define a custom property to hold the desired image height. And we'll give it a background in case the image is a little slow to load.</p> <details open=""> <summary>CSS for "Base figure display styles"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token property">--gallery-height</span><span class="token punctuation">:</span> 15rem<span class="token punctuation">;</span> <span class="token comment">/* reset figure default margin */</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-height<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>200<span class="token punctuation">,</span> 85%<span class="token punctuation">,</span> 2%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-182 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-182 img { display: block; width: 100%; } .gallery-182 figure { --gallery-height: 15rem; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-182" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>Next, we apply <code>object-fit</code> to the image along with <code>width: 100%</code> and pull in the custom property for the height so that it scales to the size of the figure. The magic of <code>object-fit: cover</code> is that no distortion occurs.</p> <details open=""> <summary>CSS for "Image display styles"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></mark> <mark class="highlight-line highlight-line-active"> <span class="token property">height</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-height<span class="token punctuation">)</span><span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-593 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-593 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-593 figure { --gallery-height: 15rem; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-593" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>If you resize the demo, you'll see that the img is now behaving much as if it were applied as a <code>background-image</code> and using <code>background-size: cover</code>. The <code>img</code> tag is acting like a container for it's own contents.</p> <blockquote> <p>For a helpful intro to <code>object-fit</code> for responsive image scaling, check out <a href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/">this earlier post from this series</a>. You might also like my <a href="https://5t3ph.dev/egobjfit">3-minute free egghead lesson on <code>object-fit</code></a>.</p> </blockquote> <p>We can improve the image sizing by upgrading to using <code>aspect-ratio</code> when supported using the native CSS feature <code>@supports</code>. When it is supported, we'll drop the <code>height</code> and swap for defining the <code>aspect-ratio</code> to use. This allows us to have more consistently sized images across viewports.</p> <details open=""> <summary>CSS for "Use aspect-ratio with @supports"</summary> <pre class="language-css"><code class="language-css"><span class="token comment">/* Add aspect-ratio custom property */</span> <span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token property">--gallery-aspect-ratio</span><span class="token punctuation">:</span> 4/3<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.gallery figure, .gallery img</span> <span class="token punctuation">{</span> <span class="token property">aspect-ratio</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gallery-aspect-ratio<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Remove height to prevent distorting aspect-ratio */</span> <span class="token property">height</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-140 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-140 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-140 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); } @supports (aspect-ratio: 1) { .gallery-140 figure, .gallery-140 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-140" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="positioning-the-caption">Positioning the Caption</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#positioning-the-caption" aria-labelledby="positioning-the-caption"><span hidden="">#</span></a></div> <p>Now at this point, the caption has flowed naturally according to DOM order below the image. This is also due to default CSS grid behavior because it's assumed that it should be in its own &quot;cell&quot; and by default grid items flow down the y-axis in rows.</p> <p>To resolve this, we create a named <code>grid-template-areas</code> for the <code>figure</code>, and assign both the <code>img</code> and <code>figcaption</code> to live there. Then, we'll use grid positioning to set <code>place-items: end</code> on the card to move the caption to the bottom right of the &quot;cell&quot;.</p> <details open=""> <summary>CSS for "Styles to position the figcaption"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure</span> <span class="token punctuation">{</span> <span class="token comment">/* ...existing styles */</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-areas</span><span class="token punctuation">:</span> <span class="token string">"card"</span><span class="token punctuation">;</span> <span class="token property">place-items</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.5rem<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure > *</span> <span class="token punctuation">{</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> card<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-74 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-74 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-74 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-74 figure, .gallery-74 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-74 figure > * { grid-area: card; } .gallery-74 figcaption { transform: translateY(100%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-74" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>You may notice the caption is also no longer visible, partly from adding <code>overflow: hidden</code> to the <code>figure</code>. Then to place the caption, we used CSS transforms to set the initial position outside the <code>figure</code>. A value of <code>100%</code> for translate will move the element <code>100%</code> relative to the axis it's placed on. So, <code>translateY(100%)</code> effectively moves the caption &quot;down&quot; out of the initial view.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="animating-the-caption">Animating the Caption</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#animating-the-caption" aria-labelledby="animating-the-caption"><span hidden="">#</span></a></div> <p>Our animation will trigger on hover, and we want it to smoothly animate in and back out again. This requires setting up the <code>transition</code> property.</p> <p>We'll define that we expect a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition">transition</a> on the <code>transform</code> property, and that the transition duration should be <code>800ms</code> and use the <code>ease-in</code> timing function.</p> <p>The <code>:hover</code> styles will actually be placed on the <code>figure</code> since it is the containing element, so we'll also add a <code>transform</code> definition that moves the caption back to it's inherent starting point by returning it to position <code>0</code> on the y-axis.</p> <details open=""> <summary>CSS for "Style and animate the figcaption"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms ease-in<span class="token punctuation">;</span> <span class="token comment">/* Visual styles for the caption */</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25em 0.5em<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px 0 0 0<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 100% / 87%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure:hover figcaption</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-287 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-287 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); } .gallery-287 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-287 figure, .gallery-287 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-287 figure > * { grid-area: card; } .gallery-287 figcaption { transform: translateY(100%); transition: transform 800ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-287 figure:hover figcaption { transform: translateY(0); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-287" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>And ta-da! We have a basic animated caption.</p> <div class="heading-wrapper h2"> <h2 id="ken-burns-image-effect">Ken Burns image effect</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#ken-burns-image-effect" aria-labelledby="ken-burns-image-effect"><span hidden="">#</span></a></div> <p>You may not have known the name, but you've seen the effect: a slow, smooth pan and zoom combo of a still image, so named due to being popularized by documentary filmmaker <a href="https://en.wikipedia.org/wiki/Ken_Burns">Ken Burns</a>.</p> <p>Using the principles we've already covered with the <code>transition</code> and <code>tranform</code> properties, we can again combine them on the <code>img</code> to add this effect on hover as well.</p> <p>We add an additional value to <code>transform</code> to set the default <code>scale</code> to 0 since on hover we'll be scaling it up so we need to set the point it starts from. We're using a generous duration of <code>1200ms</code> for the transition to take place in order to create a smooth pan and zoom effect.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 1200ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Next we add the <code>:hover</code> transition into the <code>figure</code> rule, adding both a bit more of a scale up for the zoom-in effect, in addition to pulling it back left on the x-axis to <code>-8%</code> and also a bit up on the y-axis with <code>-3%</code>. You can adjust the translate values to your taste.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure:hover img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>There's one more thing, which is that we have set our transition durations with a <code>400ms</code> difference. We can add that value as a delay for the caption. Be aware that the delay applies prior to the transition on hover, and at the end of the transition out off-hover. Personally I like this effect since it means that in both directions the animations end together.</p> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token comment">/* update to add the 400ms delay */</span></span> <mark class="highlight-line highlight-line-active"> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms 400ms ease-in<span class="token punctuation">;</span></mark> <span class="highlight-line"><span class="token punctuation">}</span></span></code></pre> <p>Altogether, here is our gallery with the Ken Burns effect on the image and caption.</p> <details open=""> <summary>CSS for "Ken Burns style animated figures"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 1200ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figure:hover img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token comment">/* added 400ms delay */</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 800ms 400ms ease-in<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-68 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-68 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-68 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-68 figure, .gallery-68 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-68 figure > * { grid-area: card; } .gallery-68 figcaption { transform: translateY(100%); transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-68 figure:hover figcaption { transform: translateY(0); } .gallery-68 figure:hover img { transform: scale(1.3) translate(-8%, -3%); } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-68" role="list"> <li> <figure> <img alt="" src="https://picsum.photos/550/300" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure> <img alt="" src="https://picsum.photos/600/450" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="dont-forget-about-focus">Don't forget about <code>:focus</code></h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#dont-forget-about-focus" aria-labelledby="dont-forget-about-focus"><span hidden="">#</span></a></div> <p>Hover is fine for mouse-users, but what about those who for various reasons use primarily their keyboard to navigate?</p> <p>The <code>li</code> element isn't inherently a focusable element, so just adding <code>:focus</code> styles will not change behavior.</p> <p>We have two options:</p> <ul> <li>If you plan to link the images anyway, wrap the <code>figure</code> with a link element and hook <code>:focus</code> styles to that</li> <li>If a link isn't needed, apply <code>tabindex=&quot;0&quot;</code> to each <code>figure</code> which will enable them as focusable elements</li> </ul> <p>We'll use the <code>tabindex</code> approach for this demo.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">></span></span></code></pre> <p>You can test this by tabbing and you will notice the standard focus halo outline.</p> <p>We'll customize the outline and also update the rules to apply the same <code>:hover</code> behavior on <code>:focus</code>.</p> <details open=""> <summary>CSS for "Reveal captions on figure:focus"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figure:focus</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">outline</span><span class="token punctuation">:</span> 2px solid white<span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token property">outline-offset</span><span class="token punctuation">:</span> 2px<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="token selector">.gallery figure:hover figcaption, .gallery figure:focus figcaption</span> <span class="token punctuation">{</span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="token selector">.gallery figure:hover img, .gallery figure:focus img</span> <span class="token punctuation">{</span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale</span><span class="token punctuation">(</span>1.3<span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span>-8%<span class="token punctuation">,</span> -3%<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-748 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-748 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-748 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-748 figure, .gallery-748 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-748 figure > * { grid-area: card; } .gallery-748 figcaption { transform: translateY(100%); transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); } .gallery-748 figure:hover figcaption, .gallery-748 figure:focus figcaption { transform: translateY(0); } .gallery-748 figure:hover img, .gallery-748 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-748 figure:focus { outline: 2px solid white; outline-offset: 2px; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-748" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="handling-for-touch-devices">Handling for touch devices</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#handling-for-touch-devices" aria-labelledby="handling-for-touch-devices"><span hidden="">#</span></a></div> <p>We've made a large assumption so far which is that users interacting with our gallery have a hover capable device <em>and</em> the motor abilities required to perform a &quot;hover&quot; on an element.</p> <p>While the current hover experience somewhat works on a touch device, if you upgrade the gallery to use links it's likely the caption wouldn't have time to show prior to the navigation event. So, let's instead change our strategy to only enable the animated links for hover-capable devices and set the default to display them.</p> <p>This is done with a media query combo to detect both hover and a &quot;fine&quot; pointing device which is likely to mean the user is primarily using a mouse, possibly a stylus. The keyword here is &quot;likely&quot; as there are devices capable of touch sometimes, and more &quot;fine&quot; pointers other times. For more info to help you make an informed decision, check out this excellent <a href="https://css-tricks.com/interaction-media-features-and-their-potential-for-incorrect-assumptions/">overview of interaction media features</a> from Patrick H. Lauke.</p> <p>We'll remove the <code>transform</code> on the <code>figcaption</code> and instead only apply it if this media query combo is valid. If you're on a touch device you'll likely see the captions by default in this next demo, or you can emulate a mobile or touch device using your browser dev tools.</p> <details open=""> <summary>CSS for "Only animate captions for non-touch devices"</summary> <pre class="language-css"><code class="language-css"><span class="highlight-line"><span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <del class="highlight-line highlight-line-remove"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span></del> <span class="highlight-line"> <span class="token comment">/* provide stacking context */</span></span> <span class="highlight-line"> <span class="token property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span> <span class="highlight-line"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">any-hover</span><span class="token punctuation">:</span> hover<span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">any-pointer</span><span class="token punctuation">:</span> fine<span class="token punctuation">)</span></span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span></span> <span class="highlight-line"> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span><span class="token punctuation">;</span></span> <span class="highlight-line"> <span class="token punctuation">}</span></span> <span class="highlight-line"><span class="token punctuation">}</span></span> <span class="highlight-line"></span></code></pre> </details> <style> .gallery-202 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-202 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-202 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-202 figure, .gallery-202 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-202 figure > * { grid-area: card; } .gallery-202 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-202 figure:hover figcaption, .gallery-202 figure:focus figcaption { transform: translateY(0); } .gallery-202 figure:hover img, .gallery-202 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-202 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-202 figcaption { transform: translateY(100%); } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-202" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="respecting-user-motion-preferences">Respecting user motion preferences</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#respecting-user-motion-preferences" aria-labelledby="respecting-user-motion-preferences"><span hidden="">#</span></a></div> <p>Some users may have a need for a &quot;reduced motion&quot; experience, which we can handle by way of a media query as well.</p> <p>The prefers-reduced-motion media query will let us remove the transition of the caption and image when the user has updated their system settings to request reduced motion. You can <a href="https://12daysofweb.dev/2021/preference-queries/">learn more about this preference media query in my overview</a>.</p> <p>When a reduced motion setting is true, we'll remove the associated <code>transition</code> and <code>transform</code> values. The result will be that the image does not have the Ken Burns effect and the caption appears instantly with no transition.</p> <details open=""> <summary>CSS for "Remove animation for prefers-reduced-motion"</summary> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.gallery *</span> <span class="token punctuation">{</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0ms <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery img</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> none <span class="token important">!important</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.gallery figcaption</span> <span class="token punctuation">{</span> <span class="token property">transition-delay</span><span class="token punctuation">:</span> 0ms<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-41 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-41 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-41 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-41 figure, .gallery-41 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-41 figure > * { grid-area: card; } .gallery-41 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-41 figure:hover figcaption, .gallery-41 figure:focus figcaption { transform: translateY(0); } .gallery-41 figure:hover img, .gallery-41 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-41 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-41 figcaption { transform: translateY(100%); } } @media (prefers-reduced-motion: reduce) { .gallery-41 * { transition-duration: 0ms !important; } .gallery-41 img { transform: none !important; } .gallery-41 figcaption { transition-delay: 0ms; } } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-41" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <div class="heading-wrapper h2"> <h2 id="optional-vignette">Optional: Vignette</h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#optional-vignette" aria-labelledby="optional-vignette"><span hidden="">#</span></a></div> <p>Another hallmark of the Ken Burns style is a vignette - the soft black gradient on the borders of the image. We can accomplish this with an inset <code>box-shadow</code>. However, an inset <code>box-shadow</code> will not work on the image element directly, so instead we apply it on an <code>:after</code> pseudo element of the <code>figure</code>:</p> <p>The vignette is positioned by applying it to the single named <code>grid-area</code> and ensuring it has a <code>height</code> and <code>width</code> to take up the whole card in addition to relative positioning to stack it above the image.</p> <details open=""> <summary>CSS for "Vignette effect"</summary> <pre class="language-css"><code class="language-css"><span class="token selector">.gallery figure::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">;</span> <span class="token property">grid-area</span><span class="token punctuation">:</span> card<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 0 2rem 1rem <span class="token function">hsl</span><span class="token punctuation">(</span>0 0% 0% / 65%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> </details> <style> .gallery-288 { list-style: none; padding: 0; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr)); gap: 1rem; } .gallery-288 img { display: block; width: 100%; object-fit: cover; height: var(--gallery-height); transform: scale(1) translate(0, 0); transition: transform 1200ms ease-in; } .gallery-288 figure { --gallery-height: 15rem; --gallery-aspect-ratio: 4/3; /* reset figure default margin */ margin: 0; height: var(--gallery-height); background-color: hsl(200, 85%, 2%); display: grid; grid-template-areas: "card"; place-items: end; border-radius: 0.5rem; overflow: hidden; } @supports (aspect-ratio: 1) { .gallery-288 figure, .gallery-288 img { aspect-ratio: var(--gallery-aspect-ratio); /* Remove height to prevent distorting aspect-ratio */ height: auto; } } .gallery-288 figure > * { grid-area: card; } .gallery-288 figcaption { transition: transform 800ms 400ms ease-in; /* Visual styles for the caption */ padding: 0.25em 0.5em; border-radius: 4px 0 0 0; background-color: hsl(0 0% 100% / 87%); /* provide stacking context */ z-index: 1; } .gallery-288 figure:hover figcaption, .gallery-288 figure:focus figcaption { transform: translateY(0); } .gallery-288 figure:hover img, .gallery-288 figure:focus img { transform: scale(1.3) translate(-8%, -3%); } .gallery-288 figure:focus { outline: 2px solid white; outline-offset: 2px; } @media (any-hover: hover) and (any-pointer: fine) { .gallery-288 figcaption { transform: translateY(100%); } } @media (prefers-reduced-motion: reduce) { .gallery-288 * { transition-duration: 0ms !important; } .gallery-288 img { transform: none !important; } .gallery-288 figcaption { transition-delay: 0ms; } } /* Vignette */ .gallery-288 figure::after { content: ""; grid-area: card; width: 100%; height: 100%; box-shadow: inset 0 0 2rem 1rem hsl(0 0% 0% / 65%); position: relative; } </style> <div class="demo"> <div class="demo--content"> <ul class="gallery-288" role="list"> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/550" /> <figcaption>Candy canes ice cream</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/400" /> <figcaption>Ice cream biscuit</figcaption> </figure> </li> <li> <figure tabindex="0"> <img alt="" src="https://picsum.photos/600" /> <figcaption>Cream biscuit marzipan</figcaption> </figure> </li> </ul> </div> </div> <p>Choose the &quot;Open in CodePen&quot; option to generate a new CodePen that includes the final styles created for this component.</p> <form action="https://codepen.io/pen/define" method="POST" target="_blank"> <input type="hidden" name="data" value='{"title":"Modern CSS Solutions - Responsive Image Gallery With Animated Captions","description":"Generated from: ModernCSS.dev/responsive-image-gallery-with-animated-captions/","tags":["moderncss"],"editors":"110","layout":"left","html":"<!-- Modern CSS Solutions - Responsive Image Gallery With Animated Captions\nGenerated from: ModernCSS.dev/responsive-image-gallery-with-animated-captions/ -->\n<ul class=\"gallery\" role=\"list\">\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/550\" />\n <figcaption>Candy canes ice cream</figcaption>\n </figure>\n </li>\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/400\" />\n <figcaption>Ice cream biscuit</figcaption>\n </figure>\n </li>\n <li>\n <figure tabindex=\"0\">\n <img alt=\"\" src=\"https://picsum.photos/600\" />\n <figcaption>Cream biscuit marzipan</figcaption>\n </figure>\n </li>\n</ul>\n","html_pre_processor":"none","css":"/* Box sizing rules */\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n/* Remove default margin */\nbody,\nh1,\nh2,\nh3,\nh4,\np {\n margin: 0;\n}\n\n/* Set core body defaults */\nbody {\n min-height: 100vh;\n text-rendering: optimizeSpeed;\n line-height: 1.5;\n font-family: system-ui, sans-serif;\n}\n\n/* Make images easier to work with */\nimg {\n display: block;\n max-width: 100%;\n}\n\n/***\n 🟣 Modern CSS Solutions Demo Styles\n */\n\n.gallery {\n list-style: none;\n padding: 0;\n margin: 0 auto;\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(20ch, 1fr));\n gap: 1rem;\n}\n\n.gallery img {\n display: block;\n width: 100%;\n object-fit: cover;\n height: var(--gallery-height);\n transform: scale(1) translate(0, 0);\n transition: transform 1200ms ease-in;\n}\n\n.gallery figure {\n --gallery-height: 15rem;\n --gallery-aspect-ratio: 4/3;\n\n /* reset figure default margin */\n margin: 0;\n height: var(--gallery-height);\n background-color: hsl(200, 85%, 2%);\n\n display: grid;\n grid-template-areas: \"card\";\n place-items: end;\n border-radius: 0.5rem;\n overflow: hidden;\n}\n\n@supports (aspect-ratio: 1) {\n .gallery figure,\n .gallery img {\n aspect-ratio: var(--gallery-aspect-ratio);\n /* Remove height to prevent distorting aspect-ratio */\n height: auto;\n }\n}\n\n.gallery figure > * {\n grid-area: card;\n}\n\n.gallery figcaption {\n transition: transform 800ms 400ms ease-in;\n\n /* Visual styles for the caption */\n padding: 0.25em 0.5em;\n border-radius: 4px 0 0 0;\n background-color: hsl(0 0% 100% / 87%);\n /* provide stacking context */\n z-index: 1;\n}\n\n.gallery figure:hover figcaption,\n.gallery figure:focus figcaption {\n transform: translateY(0);\n}\n\n.gallery figure:hover img,\n.gallery figure:focus img {\n transform: scale(1.3) translate(-8%, -3%);\n}\n\n.gallery figure:focus {\n outline: 2px solid white;\n outline-offset: 2px;\n}\n\n@media (any-hover: hover) and (any-pointer: fine) {\n .gallery figcaption {\n transform: translateY(100%);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gallery * {\n transition-duration: 0ms !important;\n }\n\n .gallery img {\n transform: none !important;\n }\n\n .gallery figcaption {\n transition-delay: 0ms;\n }\n}\n\n/* Vignette */\n.gallery figure::after {\n content: \"\";\n grid-area: card;\n width: 100%;\n height: 100%;\n box-shadow: inset 0 0 2rem 1rem hsl(0 0% 0% / 65%);\n position: relative;\n}\n","css_pre_processor":"scss","css_starter":"neither","css_prefix":"autoprefixer","head":"<meta name=&apos;viewport&apos; content=&apos;width=device-width, initial-scale=1&apos;>"}' /> <button class="button" type="submit" data-name="Responsive Image Gallery With Animated Captions" aria-label="Open Responsive Image Gallery With Animated Captions in CodePen"><span class="button__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false"> <path d="M32 10.909l-0.024-0.116-0.023-0.067c-0.013-0.032-0.024-0.067-0.040-0.1-0.004-0.024-0.020-0.045-0.027-0.067l-0.047-0.089-0.040-0.067-0.059-0.080-0.061-0.060-0.080-0.060-0.061-0.040-0.080-0.059-0.059-0.053-0.020-0.027-14.607-9.772c-0.463-0.309-1.061-0.309-1.523 0l-14.805 9.883-0.051 0.053-0.067 0.075-0.049 0.060-0.067 0.080c-0.027 0.023-0.040 0.040-0.040 0.061l-0.067 0.080-0.027 0.080c-0.027 0.013-0.027 0.053-0.040 0.093l-0.013 0.067c-0.025 0.041-0.025 0.081-0.025 0.121v9.996c0 0.059 0.004 0.12 0.013 0.18l0.013 0.061c0.007 0.040 0.013 0.080 0.027 0.115l0.020 0.067c0.013 0.036 0.021 0.071 0.036 0.1l0.029 0.067c0 0.013 0.020 0.053 0.040 0.080l0.040 0.053c0.020 0.013 0.040 0.053 0.060 0.080l0.040 0.053 0.053 0.053c0.013 0.017 0.013 0.040 0.040 0.040l0.080 0.056 0.053 0.040 0.013 0.019 14.627 9.773c0.219 0.16 0.5 0.217 0.76 0.217s0.52-0.080 0.76-0.24l14.877-9.875 0.069-0.077 0.044-0.060 0.053-0.080 0.040-0.067 0.040-0.093 0.021-0.069 0.040-0.103 0.020-0.060 0.040-0.107v-10c0-0.067 0-0.127-0.021-0.187l-0.019-0.060 0.059 0.004zM16.013 19.283l-4.867-3.253 4.867-3.256 4.867 3.253-4.867 3.253zM14.635 10.384l-5.964 3.987-4.817-3.221 10.781-7.187v6.424zM6.195 16.028l-3.443 2.307v-4.601l3.443 2.301zM8.671 17.695l5.964 3.987v6.427l-10.781-7.188 4.824-3.223v-0.005zM17.387 21.681l5.965-3.973 4.817 3.227-10.783 7.187v-6.427zM25.827 16.041l3.444-2.293v4.608l-3.444-2.307zM23.353 14.388l-5.964-3.988v-6.44l10.78 7.187-4.816 3.224z"></path> </svg></span> Open in CodePen</button> </form> <div class="heading-wrapper h2"> <h2 id="next-steps-upgrade-from-a-basic-img">Next steps: upgrade from a basic <code>img</code></h2> <a class="anchor" href="https://moderncss.dev/responsive-image-gallery-with-animated-captions/#next-steps-upgrade-from-a-basic-img" aria-labelledby="next-steps-upgrade-from-a-basic-img"><span hidden="">#</span></a></div> <p>In this simple gallery example, we just used a basic <code>img</code> element. If you'd like to learn how to use modern image formats and improve performance of your images, review my <a href="https://12daysofweb.dev/2021/image-display-elements/">guide to image display elements</a>.</p> </content>
</entry>
<entry>
<title>Totally Custom List Styles</title>
<link href="https://moderncss.dev/totally-custom-list-styles/"/>
<updated>2020-04-18T00:00:00Z</updated>
<id>https://moderncss.dev/totally-custom-list-styles/</id>
<content type="html"><p>This tutorial will show how to use CSS grid layout for easy custom list styling in addition to:</p> <ul> <li>Data attributes as the content of pseudo elements</li> <li>CSS counters for styling ordered lists</li> <li>CSS custom variables for per-list item styling</li> <li>Responsive multi-column lists</li> </ul> <blockquote> <p><strong>Update</strong>: The <code>::marker</code> pseudo selector is now well supported in modern browsers. While this tutorial includes handy CSS tips for the items listed above, you may want to <a href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker">jump to the <code>::marker</code> solution</a></p> </blockquote> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="list-html">List HTML</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#list-html" aria-labelledby="list-html"><span hidden="">#</span></a></div> <p>First we'll setup our HTML, with one <code>ul</code> and one <code>li</code>. I've included a longer bullet to assist in checking alignment, spacing, and line-heihgt.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Cake ice cream sweet sesame snaps dragée cupcake wafer cookie<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Cake ice cream sweet sesame snaps dragée cupcake wafer cookie<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span></code></pre> <p>Note the use of <code>role=&quot;list&quot;</code>. At first, it may seem extra, but we are going to remove the inherent list style with CSS. While CSS doesn't often affect the semantic value of elements, <code>list-style: none</code> can remove list semantics for some screen readers. The easiest fix is to define the <code>role</code> attribute to reinstate those semantics. You can learn more from <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">this article</a> from Scott O'Hara.</p> <div class="heading-wrapper h2"> <h2 id="base-list-css">Base List CSS</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#base-list-css" aria-labelledby="base-list-css"><span hidden="">#</span></a></div> <p>First we add a reset of list styles in addition to defining them as a grid with a gap.</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The <code>gap</code> benefit is adding space between <code>li</code>, taking the place of an older method such as <code>li + li { margin-top: ... }</code>.</p> <p>Next, we'll prepare the list items:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 0 1fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1.75em<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.25<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>We've also set list items up to use grid. And we've upgraded an older &quot;hack&quot; of using <code>padding-left</code> to leave space for an absolute positioned pseduo element with a combo of a <code>0</code> width first column and <code>gap</code>. We'll see how that works in a moment. Then we use <code>align-items: start</code> instead of the default of <code>stretch</code>, and apply some type styling.</p> <div class="heading-wrapper h2"> <h2 id="ul-data-attributes-for-emoji-bullets">UL: Data attributes for emoji bullets</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ul-data-attributes-for-emoji-bullets" aria-labelledby="ul-data-attributes-for-emoji-bullets"><span hidden="">#</span></a></div> <p>Now, this may not exactly be a scalable solution, but for fun we're going to add a custom data attribute that will define an emoji to use as the bullet for each list item.</p> <p>First, let's update the <code>ul</code> HTML:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span> Cake ice cream sweet sesame snaps dragée cupcake wafer cookie <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-icon</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 punctuation">></span></span>Unordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span></code></pre> <p>And to apply the emojis as bullets, we use a pretty magical technique where data attributes can be used as the value of the <code>content</code> property for pseudo elements:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ul li::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-icon<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Make slightly larger than the li font-size but smaller than the li gap */</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's the result, with the <code>::before</code> element inspected to help illustrate how the grid is working:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/lr1hu2lcffytfcb9d0vk.png" alt="ul styled list elements" /></p> <p>The emoji still is allowed to take up width to be visible, but effectively sits in the gap. You can experiment with setting the first <code>li</code> grid column to <code>auto</code> which will cause gap to fully be applied between the emoji column and the list text column.</p> <div class="heading-wrapper h2"> <h2 id="ol-css-counters-and-css-custom-variables">OL: CSS counters and CSS custom variables</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ol-css-counters-and-css-custom-variables" aria-labelledby="ol-css-counters-and-css-custom-variables"><span hidden="">#</span></a></div> <p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters">CSS counters</a> have been a viable solution <a href="https://caniuse.com/#search=counter">since IE8</a>, but we're going to add an extra flourish of using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS custom variables</a> to change the background color of each number as well.</p> <p>We'll apply the CSS counter styles first, naming our counter <code>orderedlist</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">counter-reset</span><span class="token punctuation">:</span> orderedlist<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">ol li::before</span> <span class="token punctuation">{</span> <span class="token property">counter-increment</span><span class="token punctuation">:</span> orderedlist<span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">counter</span><span class="token punctuation">(</span>orderedlist<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This achieves the following, which doesn't look much different than the default <code>ol</code> styling:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wb46m4n76s7p4tomridj.png" alt="ol with counter" /></p> <p>Next, we can apply some base styling to the counter numbers:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Add to li::before rule */</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"Indie Flower"</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 0.75<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 1.5rem<span class="token punctuation">;</span> <span class="token property">padding-top</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25em<span class="token punctuation">;</span></code></pre> <p>First, we apply a Google font and bump up the <code>font-size</code>. The <code>line-height</code> is half of the applied <code>line-height</code> of the <code>li</code> (at least that's what worked for this font, it may be a bit of a magic number). It aligns the number where we would like in relation to the main <code>li</code> text content.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>Then, we need to specify an explicit width. If not, the background will not appear even though the text will.</p> <p>Padding is added to fix the alignment of the text against the background.</p> <p>Now we have this:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/kcz28nyz3ly8fuvg57n9.png" alt="ol with additional styles" /></p> <p>That's certainly feeling more custom, but we'll push it a bit more by swapping the <code>background-color</code> to a CSS custom variable, like so:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">--li-bg</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">ol li::before</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--li-bg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>It will appear the same until we add inline styles to the second and third <code>li</code> to update the variable value:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</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">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--li-bg</span><span class="token punctuation">:</span> darkcyan</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span> Cake ice cream sweet sesame snaps dragée cupcake wafer cookie <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--li-bg</span><span class="token punctuation">:</span> navy</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>Ordered list item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span></code></pre> <p>And here's the final <code>ul</code> and <code>ol</code> all put together:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="WNQwEjz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="upgrade-your-algos-multi-column-lists">Upgrade your algos: Multi-column lists</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#upgrade-your-algos-multi-column-lists" aria-labelledby="upgrade-your-algos-multi-column-lists"><span hidden="">#</span></a></div> <p>Our example only had 3 short list items, but don't forget we applied grid to the base <code>ol</code> and <code>ul</code>.</p> <p>Whereas in a previous life I have done fun things with modulus in PHP to split up lists and apply extra classes to achieve evenly divided multi-column lists.</p> <p>With CSS grid, you can now apply it in three lines with inherent responsiveness, equal columns, and respect to content line length:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token comment">/* adjust the `min` value to your context */</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>auto-fill<span class="token punctuation">,</span> <span class="token function">minmax</span><span class="token punctuation">(</span>22ch<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Applying to our existing example (be sure to remove the <code>max-width</code> on the <code>li</code> first) yields:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/z9ty6z0n1fe1gu78tbis.png" alt="multi-column lists" /></p> <p>You can toggle this view by updating the <code>$multicolumn</code> variable in Codepen to <code>true</code>.</p> <div class="heading-wrapper h2"> <h2 id="gotcha-more-than-plain-text-as-li-content">Gotcha: More than plain text as <code>li</code> content</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#gotcha-more-than-plain-text-as-li-content" aria-labelledby="gotcha-more-than-plain-text-as-li-content"><span hidden="">#</span></a></div> <p>If you have more than plain text inside the <code>li</code> - including something like an innocent <code>&lt;a&gt;</code> - our grid template will break.</p> <p>However, it's a very easy solve - wrap the <code>li</code> content in a <code>span</code>. Our grid template doesn't care what the elements are, but it does only expect two elements, where the pseudo element counts as the first.</p> <div class="heading-wrapper h2"> <h2 id="upgrading-to-css-marker">Upgrading to CSS Marker</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#upgrading-to-css-marker" aria-labelledby="upgrading-to-css-marker"><span hidden="">#</span></a></div> <p>During the months after this article was originally posted, support for <code>::marker</code> became much better across all modern browsers.</p> <p>The <code>::marker</code> pseudo selector allows directly changing and styling the <code>ol</code> or <code>ul</code> list bullet/numerical.</p> <p>We can fully replace the solution for <code>ul</code> bullets using <code>::marker</code> but we have to downgrade our <code>ol</code> solution because there are only a few properties allowed for <code>::marker</code>:</p> <ul> <li><code>animation-*</code></li> <li><code>color</code></li> <li><code>content</code></li> <li><code>direction</code></li> <li><code>font-*</code></li> <li><code>transition-*</code></li> <li><code>unicode-bidi</code></li> <li><code>white-space</code></li> </ul> <div class="heading-wrapper h3"> <h3 id="unordered-list-style-with-marker">Unordered List Style With <code>::marker</code></h3> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#unordered-list-style-with-marker" aria-labelledby="unordered-list-style-with-marker"><span hidden="">#</span></a></div> <p>Since <code>content</code> is still an allowed property, we can keep our <code>data-icon</code> solution for allowing custom emoji markers 🎉</p> <p>We just need to swap <code>::before</code> to <code>::marker</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ul li::marker</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-icon<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.25em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then remove the no longer needed grid properties from the <code>li</code> and add back in some <code>padding</code> to replace the removed <code>gap</code>:</p> <pre class="language-css"><code class="language-css"><span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token comment">/* replace the grid properties with: */</span> <span class="token property">padding-left</span><span class="token punctuation">:</span> 0.5em<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Finally, we previously removed <code>margin</code> but we need to add back in some left margin to ensure space for the <code>::marker</code> to prevent it being cut off due to overflow:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* update in existing rule */</span> <span class="token selector">ol, ul</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 0 0 2em<span class="token punctuation">;</span> <span class="token comment">/* ...existing styles */</span> <span class="token punctuation">}</span></code></pre> <p>And the visual results is identical to our previous solution, as you can see in <a href="https://moderncss.dev/totally-custom-list-styles/#::marker-demo">the demo</a>.</p> <div class="heading-wrapper h3"> <h3 id="ordered-list-style-with-marker">Ordered List Style With <code>::marker</code></h3> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#ordered-list-style-with-marker" aria-labelledby="ordered-list-style-with-marker"><span hidden="">#</span></a></div> <p>For our ordered list, we can now switch and take advantage of the built-in counter.</p> <p>We also have to drop our <code>background-color</code> and <code>border-radius</code> so we'll swap to using our custom property for the <code>color</code> value. And we'll change our custom property name to <code>--marker-color</code> for clarity.</p> <p>So our reduced styles are as follows:</p> <pre class="language-css"><code class="language-css"><span class="token selector">ol</span> <span class="token punctuation">{</span> <span class="token property">--marker-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li::marker</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token function">counter</span><span class="token punctuation">(</span>list-item<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">"Indie Flower"</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.5em<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--marker-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p><em>Don't forget to update the CSS custom property name in the HTML, too!</em></p> <blockquote> <p><strong>Watch out for this gotcha</strong>: Changing the <code>display</code> property for <code>li</code> will <em>remove</em> the <code>::marker</code> pseudo element. So if you need a different display type for list contents, you'll need to apply it by nesting an additional wrapping element.</p> </blockquote> <div class="heading-wrapper h2"> <h2 id="marker-demo"><code>::marker</code> Demo</h2> <a class="anchor" href="https://moderncss.dev/totally-custom-list-styles/#marker-demo" aria-labelledby="marker-demo"><span hidden="">#</span></a></div> <p>Here's our updated custom list styles that now use <code>::marker</code>.</p> <p>Be sure to check for <a href="https://caniuse.com/?search=marker">current browser support</a> to decide which custom list style solution to use based on your unique audience! You may want to choose to use <code>::marker</code> as a progressive enhancement from one of the previously demonstrated solutions.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="KKgqeNB" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <blockquote> <p>For more details on using <code>::marker</code>, check out <a href="https://web.dev/css-marker-pseudo-element/">this excellent article</a> by Adam Argyle.</p> </blockquote> </content>
</entry>
<entry>
<title>Pure CSS Smooth-Scroll "Back to Top "</title>
<link href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/"/>
<updated>2020-04-16T00:00:00Z</updated>
<id>https://moderncss.dev/pure-css-smooth-scroll-back-to-top/</id>
<content type="html"><p>&quot;Back to top&quot; links may not be in use often these days, but there are two modern CSS features that the technique demonstrates well:</p> <ul> <li><code>position: sticky</code></li> <li><code>scroll-behavior: smooth</code></li> </ul> <p>I first fell in love with &quot;back to top&quot; links - and then learned how to do them with jQuery - on one of the most gorgeous sites of 2011: <a href="https://web.archive.org/web/20110413163553/https://webdesignerwall.com/tutorials/animated-scroll-to-top">Web Designer Wall</a>.</p> <p>The idea is to provide the user with a &quot;jump link&quot; to scroll back to the top of the website and was often used on blogs of yore.</p> <p>Here's what we will learn to achieve:</p> <p><img src="https://dev-to-uploads.s3.amazonaws.com/i/e5vl0sijw6j0zrmiddc6.gif" alt="demo of &quot;back to top&quot; link" /></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="about-position-sticky">About <code>position: sticky</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#about-position-sticky" aria-labelledby="about-position-sticky"><span hidden="">#</span></a></div> <p>This newer position value is described as follows on <a href="https://caniuse.com/#search=position%3A%20sticky">caniuse</a>:</p> <blockquote> <p>Keeps elements positioned as &quot;fixed&quot; or &quot;relative&quot; depending on how it appears in the viewport. As a result, the element is &quot;stuck&quot; when necessary while scrolling.</p> </blockquote> <p>The other important note from caniuse data is that you will need to offer it prefixed for the best support. Our demo will fallback to <code>position: fixed</code> which will achieve the main goal just a bit less gracefully.</p> <div class="heading-wrapper h2"> <h2 id="about-scroll-behavior-smooth">About <code>scroll-behavior: smooth</code></h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#about-scroll-behavior-smooth" aria-labelledby="about-scroll-behavior-smooth"><span hidden="">#</span></a></div> <p>This is a very new property, and <a href="https://caniuse.com/#search=scroll-behavior">support is relatively low</a>. This exact definition requests that scrolling behavior, particularly upon selection of an anchor link, has a smoothly animated appearance versus the default, more jarring instant jump.</p> <p>Using it offers &quot;progressive enhancement&quot; meaning that it will be a better experience for those whose browsers support it, but will also work for browsers that don't.</p> <p>Surprisingly, Safari is behind on supporting this, but the other major browsers do.</p> <div class="heading-wrapper h2"> <h2 id="set-the-stage-basic-content-html">Set the Stage: Basic content HTML</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#set-the-stage-basic-content-html" aria-labelledby="set-the-stage-basic-content-html"><span hidden="">#</span></a></div> <p>First, we need to setup some semantic markup for a basic content format:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</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>top<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- long form content here --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Back to Top link --></span> <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>back-to-top-wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>#top<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>back-to-top-link<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>Scroll to Top<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🔝<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span></code></pre> <p>We place our link after the <code>article</code>, but within <code>main</code>. It isn't specifically part of the article, and we also want it to be last in focus order.</p> <p>We also add <code>id=&quot;top&quot;</code> to the <code>&lt;header&gt;</code> and use that anchor as the <code>href</code> value for the back to top link. If you only wanted to scroll to the top of <code>&lt;main&gt;</code> you can move the id, or also attach it to an existing id near the top of your page.</p> <div class="heading-wrapper h2"> <h2 id="add-smooth-scrolling">Add smooth-scrolling</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#add-smooth-scrolling" aria-labelledby="add-smooth-scrolling"><span hidden="">#</span></a></div> <p>The first part of our objective is easy peasy and is accomplished by the following CSS rule:</p> <pre class="language-css"><code class="language-css"><span class="token comment">/* Smooth scrolling IF user doesn't have a preference due to motion sensitivities */</span> <span class="token atrule"><span class="token rule">@media</span> screen <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> no-preference<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">scroll-behavior</span><span class="token punctuation">:</span> smooth<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p><em>h/t to @mhlut for pointing out that the original solution was not accounting for <code>prefers-reduced-motion</code></em></p> <p>Previously, I had this <a href="https://css-tricks.com/snippets/jquery/smooth-scrolling/">CSS-Tricks article</a> bookmarked to accomplish this with jQuery and vanilla JS. The article has been around a while, and kudos to that team for continually updating articles like that when new methods are available 👍</p> <p>I have found some oddities, such as when you visit a page that includes an <code>#anchor</code> in the URL it still performs the smooth scroll which may not actually be desirable for your scenario.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="style-the-back-to-top-link">Style the &quot;Back to Top&quot; link</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#style-the-back-to-top-link" aria-labelledby="style-the-back-to-top-link"><span hidden="">#</span></a></div> <p>Before we make it work, let's apply some basic styling to the link. For fun, I used an emoji but you can swap for an SVG icon for more control over styling.</p> <pre class="language-css"><code class="language-css"><span class="token selector">.back-to-top-link</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 3rem<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> #d6e3f0<span class="token punctuation">;</span> <span class="token comment">/* emoji don't behave like regular fonts so this helped position it correctly */</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This gives us a very basic round button. In the full Codepen, I've added additional &quot;pretty&quot; styles and <code>:hover</code> and <code>:focus</code> styling, but those aren't essential.</p> <p>Next, you may wonder why we added a wrapper for this link. The reason is we need to use it to basically create a &quot;scroll track&quot; for the link to live within.</p> <p>The way <code>position: sticky</code> is designed, it picks up the element from where it's positioned in the DOM. Then, it respects a &quot;top&quot; value to place it relative to the viewport. However, we placed the link at the end of the document, so it would essentially never be picked up without some assistance.</p> <p>We will use the wrapper along with <code>position: absolute</code> to alter the link's position to visually higher up on the page. Thankfully, the browser uses this visually adjusted position - aka when it enters the viewport - to calculate when to &quot;stick&quot; the link.</p> <p>So, our wrapper styles look like this:</p> <pre class="language-scss"><code class="language-scss"><span class="token comment">/* How far of a scroll travel within &lt;main> prior to the link appearing */</span> <span class="token property"><span class="token variable">$scrollLength</span></span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token selector">.back-to-top-wrapper </span><span class="token punctuation">{</span> <span class="token comment">// uncomment to visualize "track"</span> <span class="token comment">// outline: 1px solid red;</span> <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token variable">$scrollLength</span><span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token comment">// Optional, extends the final link into the</span> <span class="token comment">// footer at the bottom of the page</span> <span class="token comment">// Set to `0` to stop at the end of `main`</span> <span class="token property">bottom</span><span class="token punctuation">:</span> -5em<span class="token punctuation">;</span> <span class="token comment">// Required for best support in browsers not supporting `sticky`</span> <span class="token property">width</span><span class="token punctuation">:</span> 3em<span class="token punctuation">;</span> <span class="token comment">// Disable interaction with this element</span> <span class="token property">pointer-events</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The last definition may also be new to you: <code>pointer-events: none</code>. This essentially lets interaction events like clicks &quot;fall through&quot; this element so that it doesn't block the rest of the document. We wouldn't want this wrapper to accidentally block links in the content, for example.</p> <p>With this in place, we now see the link overlapping the content a little bit below the initial viewport content. Let's add some styling to <code>&lt;main&gt;</code> to prevent this overlap, and also add <code>position: relative</code> which is necessary for best results given the absolute link wrapper:</p> <pre class="language-scss"><code class="language-scss"><span class="token selector">main </span><span class="token punctuation">{</span> <span class="token comment">// leave room for the "scroll track"</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 3rem<span class="token punctuation">;</span> <span class="token comment">// required to make sure the `absolute` positioning of</span> <span class="token comment">// the anchor wrapper is indeed `relative` to this element vs. the body</span> <span class="token property">position</span><span class="token punctuation">:</span> relative<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 50rem<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 2rem auto<span class="token punctuation">;</span> <span class="token comment">// Optional, clears margin if last element is a block item</span> <span class="token selector">*:last-child </span><span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>All that's left is to revisit the link to add the necessary styles for the positioning to full work:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.back-to-top-link</span> <span class="token punctuation">{</span> // `fixed` is fallback when `sticky` not supported <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> // preferred positioning<span class="token punctuation">,</span> requires prefixing for most support<span class="token punctuation">,</span> and not supported on Safari // <span class="token atrule"><span class="token rule">@link</span> <span class="token property">https</span><span class="token punctuation">:</span>//caniuse.com/#search=position%3A%20sticky <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span></span> // reinstate clicks <span class="token property">pointer-events</span><span class="token punctuation">:</span> all<span class="token punctuation">;</span> // achieves desired positioning within the viewport // relative to the top of the viewport once `sticky` takes over<span class="token punctuation">,</span> or always if `fixed` fallback is used <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>100vh - 5rem<span class="token punctuation">)</span><span class="token punctuation">;</span> // ... styles written earlier <span class="token punctuation">}</span></code></pre> <p>The <code>fixed</code> fallback means that browsers that don't support <code>sticky</code> positioning will always show the link. When <code>sticky</code> is supported, the fully desirable effect happens which is the link will not appear until the <code>$scrollLength</code> is passed. With <code>sticky</code> position, once the top of the wrapper is in the viewport, the link is &quot;stuck&quot; and scrolls with the user.</p> <p>Notice we also reinstated <code>pointer-events</code> with the <code>all</code> value so that interaction with the link actually work.</p> <p>And here's the final result - view in non-Safari for best results.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="OJyyqWR" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="known-issues">Known Issues</h2> <a class="anchor" href="https://moderncss.dev/pure-css-smooth-scroll-back-to-top/#known-issues" aria-labelledby="known-issues"><span hidden="">#</span></a></div> <p>If you have short-form content, this doesn't fail very gracefully on a shorter device viewport. You may think - as did I - to use <code>overflow: hidden</code>. But that, unfortunately, prevents <code>position: sticky</code> from working entirely ☹️ So in a &quot;real world&quot; scenario, you may have to offer an option to toggle this behavior per article, or perform a calculation to determine if an article meets a length requirement before injecting it in the template.</p> <p>Drop a comment if you know of or find any other &quot;gotchas&quot; with these methods!</p> </content>
</entry>
<entry>
<title>CSS-Only Full-Width Responsive Images 2 Ways</title>
<link href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/"/>
<updated>2020-04-14T00:00:00Z</updated>
<id>https://moderncss.dev/css-only-full-width-responsive-images-2-ways/</id>
<content type="html"><p>In the not to distant past when jQuery was King of the Mountain and CSS3 was still worth being designated as such, the most popular tool for responsive background images was the <a href="https://www.jquery-backstretch.com/">Backstretch jQuery plugin</a>.</p> <p>I used this plugin on ~30 sites prior to the following property becoming more supported (aka IE &lt; 9 dropping in total market share):</p> <pre class="language-css"><code class="language-css"><span class="token property">background-size</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></code></pre> <p>According to <a href="https://caniuse.com/#feat=mdn-css_properties_background-size_contain_and_cover">caniuse.com</a>, this property and value have been well supported for over 9 years! But websites that are intertwined with using Backstretch or another homegrown solution may not yet have updated.</p> <p>The alternative method makes use of the standard <code>img</code> tag, and uses the magic of:</p> <pre class="language-css"><code class="language-css"><span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span></code></pre> <p>Let's look at how to use each solution, and learn when to select one over the other.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="using-background-size-cover">Using <code>background-size: cover</code></h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#using-background-size-cover" aria-labelledby="using-background-size-cover"><span hidden="">#</span></a></div> <p>A decade of my background was creating highly customized WordPress themes and plugins for enterprise websites. So using the example of templated cards, here's how you might set up using the <code>background-size: cover</code> solution.</p> <p>First, the HTML, where the image is inserted into the <code>style</code> attribute as a <code>background-image</code>. An <code>aria-label</code> is encouraged to take the place of the <code>alt</code> attribute that would normally be present on a regular <code>img</code> tag.</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</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>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <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>card__img<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>Preview of Whizzbang Widget<span class="token punctuation">"</span></span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">background-image</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://placeimg.com/320/240/tech<span class="token punctuation">)</span></span></span><span class="token punctuation">"</span></span></span> <span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <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>card__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Whizzbang Widget SuperDeluxe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Liquorice candy macaroon soufflé jelly cake. Candy canes ice cream biscuit marzipan. Macaroon pie sesame snaps jelly-o. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Add to Cart<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>The relevant corresponding CSS would be the following, where <code>padding-bottom</code> is one weird trick that is used to set a 16:9 ratio on the div containing the image:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__img</span> <span class="token punctuation">{</span> <span class="token property">background-size</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">background-position</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> // 16<span class="token punctuation">:</span>9 ratio <span class="token property">padding-bottom</span><span class="token punctuation">:</span> 62.5%<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Here's this solution altogether:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvvVeo" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="using-object-fit-cover">Using <code>object-fit: cover</code></h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#using-object-fit-cover" aria-labelledby="using-object-fit-cover"><span hidden="">#</span></a></div> <p>This solution is a newer player, and is not available to you if you need to support IE &lt; Edge 16, according to <a href="https://caniuse.com/#search=object-fit">caniuse data</a> without a polyfill.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <p>This style is placed directly on the <code>img</code> tag, so we update our card HTML to the following, swapping the <code>aria-label</code> to <code>alt</code>:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</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>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</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>card__img<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Preview of Whizzbang Widget<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://placeimg.com/320/240/tech<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span> <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>card__content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span><span class="token punctuation">></span></span>Whizzbang Widget SuperDeluxe<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span> Liquorice candy macaroon soufflé jelly cake. Candy canes ice cream biscuit marzipan. Macaroon pie sesame snaps jelly-o. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span> <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>button<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Add to Cart<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">></span></span></code></pre> <p>Then our updated CSS swaps to using the <code>height</code> property to constrain the image so that any size image conforms to the constrained ratio. If the inherent size of the image is greater than the constrained image size, the <code>object-fit</code> property takes over and by default centers the image within the bounds created by the card container + the <code>height</code> definition:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.card__img</span> <span class="token punctuation">{</span> <span class="token property">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 30vh<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And here's the result:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="VwvvVqa" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="when-to-use-each-solution">When to Use Each Solution</h2> <a class="anchor" href="https://moderncss.dev/css-only-full-width-responsive-images-2-ways/#when-to-use-each-solution" aria-labelledby="when-to-use-each-solution"><span hidden="">#</span></a></div> <p>If you have to support older versions of IE, then without a polyfill you are limited to the <code>background-size</code> solution (it pains me to say this in 2020, but this is a possibility particularly for enterprise and education industries).</p> <p>Both solutions enable a full-width responsive image based on a width:height ratio you control.</p> <p><strong>Choose <code>background-size</code> if</strong>:</p> <ul> <li>applying to a container expected to hold additional content, ex. a website header background</li> <li>to apply additional effect styles via pseudo elements which are not available to the <code>img</code> element</li> <li>to more gracefully apply a uniform size of image</li> <li>the image is purely decorative and the inherent <code>img</code> semantics are not needed</li> </ul> <p><strong>Choose <code>object-fit</code> if</strong>:</p> <ul> <li>using a standard <code>img</code> is best for your context in order to maintain all semantics provided by an image</li> </ul> </content>
</entry>
<entry>
<title>Equal Height Elements: Flexbox vs. Grid</title>
<link href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/"/>
<updated>2020-04-09T00:00:00Z</updated>
<id>https://moderncss.dev/equal-height-elements-flexbox-vs-grid/</id>
<content type="html"><p>Once upon a time (approximately 2013), I wrote a jQuery plugin to calculate equal height columns. It ensured that the very specific scenario of a row with three columns would keep the content boxes equal height no matter the length of the content they contained. The dominant layout method at the time - floats - did not handle this problem.</p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-solution">Flexbox Solution</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#flexbox-solution" aria-labelledby="flexbox-solution"><span hidden="">#</span></a></div> <p>When flexbox arrived on the scene, this became possible with:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.flexbox</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Amazing! By default, direct children line up in a row and have a &quot;stretch&quot; applied so they are equal height 🙌</p> <p>But then you add two <code>.column</code> divs as children and... the contents of the columns appear unequal again 😔</p> <p>The fix is:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.flexbox</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token selector">// Ensure content elements fill up the .column .element</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Now the columns will appear equal height and grow with the content of <code>.element</code>.</p> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h2"> <h2 id="grid-solution">Grid Solution</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#grid-solution" aria-labelledby="grid-solution"><span hidden="">#</span></a></div> <p>With grid, we encounter similar behavior:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> // Essentially switch the default axis <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Similar to flexbox, direct children will be equal height, but their children need the height definition added just like in the flexbox solution:</p> <pre class="language-css"><code class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token selector">// Ensure content elements fill up the .column .element</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>Here's a demo of both solutions, as well as additional demos for defining a set amount of columns per row as described below:</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="BaoamwO" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which is Better?</h2> <a class="anchor" href="https://moderncss.dev/equal-height-elements-flexbox-vs-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>For purely solving for equal height elements, the advantage of flexbox is the default axis immediately enables side-by-side columns, whereas grid needs to be explicitly set. However, elements will not inherently be equal-width as well (which may be an advantage depending on type of content, for example navigation links).</p> <p>The advantage of grid is inherently equal-width elements if that is desirable. An additional advantage is when you don't want auto-flow but instead want to define a set max number of columns per &quot;row&quot;. In this case, grid layout easily handles the math to distribute the columns vs. a flexbox solution requiring defining the calculation to restrict the number of columns.</p> <p>Updating our <code>.grid</code> solution to handle for defining a max number of 3 <code>.column</code> per row is as simple as:</p> <pre class="language-css"><code class="language-css"><span class="token selector">&amp;.col-3</span> <span class="token punctuation">{</span> <span class="token property">gap</span><span class="token punctuation">:</span> $col_gap<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>3<span class="token punctuation">,</span> 1fr<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Whereas one (very basic) option for flexbox would be:</p> <pre class="language-css"><code class="language-css">$<span class="token property">col_gap</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token selector">.flexbox.col-3</span> <span class="token punctuation">{</span> // Explicitly needs to be defined to wrap // overflow items to the next virtual row <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token selector">.column</span> <span class="token punctuation">{</span> // <span class="token string">"hack"</span> for no gap property <span class="token property">margin</span><span class="token punctuation">:</span> $col_gap/2<span class="token punctuation">;</span> <span class="token selector">// define calculation for browser to use on the width max-width: calc((100% / 3) - #</span><span class="token punctuation">{</span>$col_gap<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>You would also need to consider how these solutions are handled responsively, but that's a bit out of scope of this article :)</p> </content>
</entry>
<entry>
<title>Keep the Footer at the Bottom: Flexbox vs. Grid</title>
<link href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/"/>
<updated>2020-04-09T00:00:00Z</updated>
<id>https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/</id>
<content type="html"><p>For many years, I constantly referred to <a href="https://matthewjamestaylor.com/bottom-footer">this article</a> by Matthew James Taylor for a method to keep a webpage footer at the bottom of the page regardless of the main content length. This method relied on setting an explicit footer height, which is not scalable but a very good solution BF (Before Flexbox).</p> <p>If you mostly deal with SPA development, you may be confused about why this problem is still around, but it's still a possibility to find your footer floating up for:</p> <ul> <li>login pages</li> <li>blog/news articles (with no ads...)</li> <li>interstitial pages of a flow like confirming actions</li> <li>product listing pages</li> <li>calendar event details</li> </ul> <p>There are two ways to handle this with modern CSS: flexbox and grid.</p> <p>Here's the demo, defaulted to the flexbox method. If you open the full Codepen, you can swap the <code>$method</code> variable to <code>grid</code> to view that alternative.</p> <p>Read on past the demo to learn about each method.</p> <p class="codepen" data-height="265" data-theme-id="default" data-default-tab="result" data-user="5t3ph" data-slug-hash="abvboxz" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"><span>By Stephanie Eckles (<a href="https://codepen.io/5t3ph">@5t3ph</a>)</span></p> <div class="carbon-ad"> <script async="" type="text/javascript" src="https://cdn.carbonads.com/carbon.js?serve=CE7I52QE&placement=moderncssdev" id="_carbonads_js"></script> </div> <div class="heading-wrapper h2"> <h2 id="flexbox-method">Flexbox Method</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#flexbox-method" aria-labelledby="flexbox-method"><span hidden="">#</span></a></div> <p>This method is accomplished by defining:</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">footer</span> <span class="token punctuation">{</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Optional */</span> <span class="token selector">main</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token comment">/* or: align-self: center */</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 80ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="promo promo--centered"><p><strong style="color: var(--color-blue);">Join my newsletter</strong> for article updates, CSS tips, and front-end resources!</p> <form name="newsletter" action="https://moderncss.dev/success" class="form" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p hidden=""> <label>Don’t fill this out if you're human: <input name="bot-field" /></label> </p> <div class="form-inline"> <div class="form-group"> <label for="email">Email</label> <input required="" type="text" id="email" name="email" class="form-field" /> </div> <button class="button" type="submit">Subscribe</button> </div> </form> </div> <div class="heading-wrapper h3"> <h3 id="how-it-works">How it Works</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#how-it-works" aria-labelledby="how-it-works"><span hidden="">#</span></a></div> <p>First, we ensure the <code>body</code> element will stretch to at least the full height of the screen with <code>min-height: 100vh</code>. This will not trigger overflow if content is short (exception: <a href="https://css-tricks.com/some-things-you-oughta-know-when-working-with-viewport-units/">certain mobile browsers</a>) and it will allow content to continue stretching the height as needed.</p> <p>Setting <code>flex-direction: column</code> keeps the behavior of normal document flow in terms of retaining stacked block-elements (which assumes direct children of <code>body</code> are all indeed block elements).</p> <p>The advantage of flexbox is in leveraging the <code>margin: auto</code> behavior. This one weird trick will cause the margin to fill any space between the item it is set on and its nearest sibling in the corresponding direction. Setting <code>margin-top: auto</code> effectively pushes the footer to the bottom of the screen.</p> <div class="heading-wrapper h3"> <h3 id="gotcha">Gotcha</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#gotcha" aria-labelledby="gotcha"><span hidden="">#</span></a></div> <p>In the demo, I've added an <code>outline</code> to <code>main</code> to demonstrate that in the flexbox method the <code>main</code> element doesn't fill the height. Which is why we have to use the <code>margin-top: auto</code> trick. This is not likely to matter to you, but if it does, see the grid method which stretches the <code>main</code> to fill the available space.</p> <div class="heading-wrapper h2"> <h2 id="grid-method">Grid Method</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#grid-method" aria-labelledby="grid-method"><span hidden="">#</span></a></div> <p>This method is achieved by setting:</p> <pre class="language-css"><code class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> auto 1fr auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Optional */</span> <span class="token selector">main</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 auto<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 80ch<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <div class="heading-wrapper h3"> <h3 id="how-it-works-1">How it Works</h3> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#how-it-works-1" aria-labelledby="how-it-works-1"><span hidden="">#</span></a></div> <p>We retain the <code>min-height: 100vh</code> for this method, but we then use of <code>grid-template-rows</code> to space things correctly.</p> <p>This method's weird trick is using the special grid unit <code>fr</code>. The <code>fr</code> means &quot;fraction&quot; and using it requests that the browser computes the available &quot;fraction&quot; of space that is left to distribute to that column or row. In this case, it fills all available space between the header and footer, which also solves the &quot;gotcha&quot; from the flexbox method.</p> <div class="heading-wrapper h2"> <h2 id="which-is-better">Which is Better?</h2> <a class="anchor" href="https://moderncss.dev/keep-the-footer-at-the-bottom-flexbox-vs-grid/#which-is-better" aria-labelledby="which-is-better"><span hidden="">#</span></a></div> <p>After seeing grid, you may have a moment of thinking it's clearly superior. However, if you add more elements between the header and footer you need to update your template (or ensure there's always a wrapping element such as a <code>div</code> to not affect any nested semantics/hierarchy).</p> <p>On the other hand, the flexbox method is usable across various templates with multiple block elements in the midsection - for example, a series of <code>&lt;article&gt;</code> elements instead of a single <code>&lt;main&gt;</code> for an archive page.</p> <p>So as with all techniques, it depends on the project :) But we can all agree it's amazing to have these modern CSS layout methods!</p> </content>
</entry>
</feed>