utilitybendFront-End developer from Belgium with a passion for HTML, CSS, UI, UX, accessibility and developer happinesshttps://utilitybend.com/en-usPutting my money where my mouth is: a redesign built on progressive enhancementhttps://utilitybend.com/blog/putting-my-money-where-my-mouth-is-a-redesign-built-on-progressive-enhancement/https://utilitybend.com/blog/putting-my-money-where-my-mouth-is-a-redesign-built-on-progressive-enhancement/I talk about progressive enhancement a lot. Like, a lot. So when it came time to redesign my own site, I figured it was time to actually practice what I preach. What started as a typography fix turned into a full redesign powered by view transitions, corner-shape, @property, scroll-state queries, anchor positioning, and more. All progressively enhanced. All without a single polyfill.Wed, 04 Mar 2026 00:00:00 GMT<picture> <source srcset="/_astro/visual.COybGa0W_Z2193nP.avif 375w, /_astro/visual.COybGa0W_1Lq87O.avif 480w, /_astro/visual.COybGa0W_loxN5.avif 680w, /_astro/visual.COybGa0W_26kjbR.avif 800w, /_astro/visual.COybGa0W_1hcqG8.avif 980w, /_astro/visual.COybGa0W_WTwfL.avif 1024w, /_astro/visual.COybGa0W_16sSfL.avif 1660w, /_astro/visual.COybGa0W_ZMfgL.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.COybGa0W_g3saI.webp 375w, /_astro/visual.COybGa0W_Z11yu7y.webp 480w, /_astro/visual.COybGa0W_Z2rA4ri.webp 680w, /_astro/visual.COybGa0W_ZGEj3v.webp 800w, /_astro/visual.COybGa0W_Z1vMbyf.webp 980w, /_astro/visual.COybGa0W_vtxlX.webp 1024w, /_astro/visual.COybGa0W_E2TlX.webp 1660w, /_astro/visual.COybGa0W_1iAJk8.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.COybGa0W_k5VFe.png" srcset="/_astro/visual.COybGa0W_Z24ox1E.png 375w, /_astro/visual.COybGa0W_1IaDu0.png 480w, /_astro/visual.COybGa0W_i94ag.png 680w, /_astro/visual.COybGa0W_234Oy3.png 800w, /_astro/visual.COybGa0W_1dVW3j.png 980w, /_astro/visual.COybGa0W_ZFWwsD.png 1024w, /_astro/visual.COybGa0W_ZxoasD.png 1660w, /_astro/visual.COybGa0W_ZpKGE8.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture><p>If you’ve been reading my articles or attended one of my talks, you probably know that I’m quite the fan of progressive enhancement. I’ve written about <a href="https://utilitybend.com/blog/why-you-should-be-using-new-css-features-today">why you should be using new CSS features today</a>, I’ve done conference demos showing off <code>@supports</code> and feature detection, and I’ve been trying to advocate the idea that we can use new CSS features without waiting for full browser support ever since I read <a href="https://www.goodreads.com/book/show/9537411-hardboiled-web-design">Andy Clarke’s “Hardboiled web design”</a> in 2010. So when I looked at my own blog and realized it wasn’t exactly living up to that message… well, it was time to do something about it.</p> <p>It started out as a “just a quick typography fix” and turned into a full redesign (as it goes with side projects, nothing ever stays small). I wanted to share some of the things I did, some of the features I used, and how progressive enhancement plays into all of it. The CSS isn’t perfect (yet), it’s a side project after all and I will keep tinkering with it, but I had a lot of fun building this.</p> <picture> <source srcset="/_astro/old-version.BRApPF6b_Z13oSoD.avif 320w, /_astro/old-version.BRApPF6b_Z1mtC75.avif 480w, /_astro/old-version.BRApPF6b_Z1xy7v0.avif 800w, /_astro/old-version.BRApPF6b_ZjHrCp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/old-version.BRApPF6b_ZJY6RP.webp 320w, /_astro/old-version.BRApPF6b_Z143PAh.webp 480w, /_astro/old-version.BRApPF6b_Z1f8kYc.webp 800w, /_astro/old-version.BRApPF6b_Z1hF6B.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/old-version.BRApPF6b_Z1GAajC.jpg" srcset="/_astro/old-version.BRApPF6b_Z2n9Lg3.jpg 320w, /_astro/old-version.BRApPF6b_2nWCPr.jpg 480w, /_astro/old-version.BRApPF6b_2cS8rw.jpg 800w, /_astro/old-version.BRApPF6b_Z1DsktO.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The previous version of the utilitybend blog, showing a dark themed article page with code blocks and a sidebar" loading="lazy" decoding="async" fetchpriority="auto" width="1024" height="543"> </picture> <h2 id="it-started-with-typography">It started with typography</h2> <p>This one was on top of my list for quite some time. The font pairing on the old site just didn’t feel right anymore. The hierarchy was a bit off, and the reading experience could be better. I decided to go with <a href="https://fonts.google.com/specimen/Young+Serif" target="_blank" rel="noreferrer noopener">Young Serif</a> for headings, it has this warm character without being overly decorative. <a href="https://rsms.me/inter/" target="_blank" rel="noreferrer noopener">Inter</a> for body text because it reads beautifully on screens, and I kept Source Code Pro for code blocks because the only thing I did love on the previous version was the code blocks.</p> <p>Beyond just swapping fonts, I also reworked the entire type scale. Once again, it uses fluid sizing with <code>clamp()</code>, so the typography scales smoothly between viewport sizes without breakpoints:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token property">--s-font-size-base</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1rem<span class="token punctuation">,</span> 0.46vw + 0.91rem<span class="token punctuation">,</span> 1.31rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--s-line-height-base</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.625rem<span class="token punctuation">,</span> 0.74vw + 1.48rem<span class="token punctuation">,</span> 2.13rem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--s-font-size-h1</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>1.3125rem<span class="token punctuation">,</span> 3.15vw + 0.68rem<span class="token punctuation">,</span> 3.44rem<span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>I also rethought the color palette while I was at it. The new design uses a warm cream base for light mode insted of being just “harsh” and a deep indigo for dark mode, built on <code>oklch</code> and <code>color-mix()</code> to keep things perceptually consistent across both themes.</p> <p>But there was this other thing that was bugging me…</p> <h2 id="from-scss-to-plain-css-and-organizing-the-project">From SCSS to plain CSS, and organizing the project</h2> <p>I dropped SCSS entirely. The whole codebase is now plain CSS with native nesting. No Sass compilation, but I did rely on postCSS for the very basic things such as imports and minification. I wrote an article years ago wondering whether it was <a href="https://utilitybend.com/blog/is-it-time-to-stop-pre-processing-css-and-use-post-processing-instead">time to stop pre-processing CSS</a>, and I believe this time was the right time.</p> <p>For the architecture, I went with CSS cascade layers following a sort of “atomic design” approach:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@layer</span> vendor<span class="token punctuation">,</span> tokens<span class="token punctuation">,</span> reset<span class="token punctuation">,</span> base<span class="token punctuation">,</span> atoms<span class="token punctuation">,</span> molecules<span class="token punctuation">,</span> organisms<span class="token punctuation">,</span> pages<span class="token punctuation">,</span> utilities<span class="token punctuation">;</span></span> </code></pre> <p>I chose to load vendors first (don’t have many of those to begin with except for Prism). Makes it easier to override them if needed.</p> <p>Each layer has its own set of files. One decision I’m quite happy with is putting some progressive enhancement features in their own dedicated files (for now). Things like <code>corner-shapes.css</code> and <code>view-transitions.css</code> each have their own file, which gives me a clear overview of what’s enhanced and makes it easy to manage or remove things if needed. It’s a small thing, but it helped a lot while the updates keeps growing (and believe me, they did).</p> <h2 id="corner-shape-squircles-and-scoops">Corner-shape: squircles and scoops</h2> <p>I have to admit, this one brings a smile to my face every time I look at it. If you’ve read my <a href="https://utilitybend.com/blog/css-corner-shape-books-talks-and-happy-holidays">end-of-year article on corner-shape</a>, you know I’m a fan. The <code>corner-shape</code> property lets you control how border-radius curves are drawn, giving you squircles instead of the standard circular arcs.</p> <p>The entire implementation lives behind <code>@supports</code>, which makes it progressive enhancement in its purest form. If your browser supports it, you get those smooth curves. If not, you still get regular <code>border-radius</code> and you’re none the wiser:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">corner-shape</span><span class="token punctuation">:</span> squircle<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.intro-text</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> squircle<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.home-latest .article-preview</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> squircle<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">pre[class*=&quot;language-&quot;]</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> squircle<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.nav-category, .related, .comments</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> squircle<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>One thing worth mentioning: when <code>corner-shape</code> is supported, the existing <code>border-radius</code> values often look too subtle because the squircle curves are smoother by nature. So I bump up the radius primitives inside the <code>@supports</code> block to compensate. It’s not the most elegant solution, but it does the job. Yes, a purist would say I shouldn’t change a primitive…</p> <p>My favorite part though, is the button hover effect. When you hover a button, the corner-shape transitions from <code>round</code> to <code>round round round scoop</code>, giving the bottom-left corner a playful inward curve:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.btn:not(.btn-link)</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> round<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> corner-shape 0.24s ease<span class="token punctuation">,</span> border-radius 0.24s ease<span class="token punctuation">;</span> <span class="token selector">&amp;:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">corner-shape</span><span class="token punctuation">:</span> round round round scoop<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h2 id="view-transitions-direction-aware-navigation">View transitions: direction-aware navigation</h2> <p>Cross-document view transitions were the feature I was most excited to add. The foundation is surprisingly simple:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@view-transition</span></span> <span class="token punctuation">{</span> <span class="token property">navigation</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>That gives you a basic crossfade between pages. But I wanted a bit more control. The header and footer are persistent elements, so they get <code>animation: none</code> (no need for them to animate). The main content fades, and the interesting bit is what happens with article previews.</p> <p>Each article preview’s image, title, lead text, and date get a <code>view-transition-class</code>. When you click an article from the homepage, those elements morph into their corresponding positions on the article page. The image smoothly scales and repositions, the title flows to its new location. It creates this continuity that makes the site feel a bit more cohesive:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.article-preview-image</span> <span class="token punctuation">{</span> <span class="token property">view-transition-class</span><span class="token punctuation">:</span> post-image<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.article-preview-header h2</span> <span class="token punctuation">{</span> <span class="token property">view-transition-class</span><span class="token punctuation">:</span> post-title<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.article-body &gt; figure:first-child</span> <span class="token punctuation">{</span> <span class="token property">view-transition-class</span><span class="token punctuation">:</span> post-image<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.article-header h1</span> <span class="token punctuation">{</span> <span class="token property">view-transition-class</span><span class="token punctuation">:</span> post-title<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I also added direction-aware transitions using <code>:active-view-transition-type()</code>. Going deeper into the site (clicking an article) slides the content to the left. Going back slides it to the right. Navigating between pages at the same level does a subtle crossfade:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:active-view-transition-type(forward)</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::view-transition-old(page-content)</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> 0.25s <span class="token function">cubic-bezier</span><span class="token punctuation">(</span>0.4<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 1<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> both vt-slide-out-left<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::view-transition-new(page-content)</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> 0.3s 0.05s <span class="token function">cubic-bezier</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.2<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> both vt-slide-in-right<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token selector">:active-view-transition-type(back)</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::view-transition-old(page-content)</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> 0.25s <span class="token function">cubic-bezier</span><span class="token punctuation">(</span>0.4<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 1<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> both vt-slide-out-right<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::view-transition-new(page-content)</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> 0.3s 0.05s <span class="token function">cubic-bezier</span><span class="token punctuation">(</span>0<span class="token punctuation">,</span> 0<span class="token punctuation">,</span> 0.2<span class="token punctuation">,</span> 1<span class="token punctuation">)</span> both vt-slide-in-left<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>And of course, all of this respects <code>prefers-reduced-motion</code>. If a user has that preference set, every transition duration drops to essentially zero. Progressive enhancement goes both ways: enhance for those who want it, back off for those who don’t.</p> <p>One more thing on view transitions: each article preview and its corresponding article page share <code>data-vt</code> attributes on matching elements (title, image, date, lead text). Instead of writing a unique <code>view-transition-name</code> for every single article in CSS, I used the enhanced <code>attr()</code> to pull the transition name straight from the HTML:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[data-vt]</span> <span class="token punctuation">{</span> <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-vt <span class="token function">type</span><span class="token punctuation">(</span>&lt;custom-ident&gt;<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This is the enhanced version of <code>attr()</code>, not the old string-only one we’ve had forever. It can now resolve to typed CSS values like <code>&lt;custom-ident&gt;</code>, <code>&lt;color&gt;</code>, <code>&lt;length&gt;</code>, and more. This is part of interop 2026, which means browser support should be solid by the end of 2026. I decided to use this for a couple of updates because of this reason.</p> <h2 id="the-orbs-property-meets-scroll-driven-animations">The orbs: @property meets scroll-driven animations</h2> <p>The old homepage had these colorful orb-like blobs in the background. They used a <code>mix-blend-mode</code> which used to have a “wow factor” when I first created this website, It also used scroll-driven-animations to make them move but that was already an updates as I used GSAP before that. I liked the vibe of those orbs, they gave the page some personality. But they were static and felt a bit dated (and I really wanted a hue shift somewhere…).</p> <picture> <source srcset="/_astro/orb-dark-1.EymgCOpy_1WEzyG.avif 320w, /_astro/orb-dark-1.EymgCOpy_Z272NLI.avif 480w, /_astro/orb-dark-1.EymgCOpy_20IiCf.avif 800w, /_astro/orb-dark-1.EymgCOpy_1GekQO.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/orb-dark-1.EymgCOpy_28gdLa.webp 320w, /_astro/orb-dark-1.EymgCOpy_Z1Vrazf.webp 480w, /_astro/orb-dark-1.EymgCOpy_2bjVOI.webp 800w, /_astro/orb-dark-1.EymgCOpy_1QOY4i.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/orb-dark-1.EymgCOpy_ZhdYom.jpg" srcset="/_astro/orb-dark-1.EymgCOpy_B0xvU.jpg 320w, /_astro/orb-dark-1.EymgCOpy_1BuhYr.jpg 480w, /_astro/orb-dark-1.EymgCOpy_E4gzt.jpg 800w, /_astro/orb-dark-1.EymgCOpy_kziO3.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The old utilitybend homepage in dark mode, showing large purple and indigo orb blobs in the background" loading="lazy" decoding="async" fetchpriority="auto" width="1024" height="540"> </picture> <picture> <source srcset="/_astro/orb-dark-2.CS6zkNzr_Z2fWauD.avif 320w, /_astro/orb-dark-2.CS6zkNzr_Zyvg2E.avif 480w, /_astro/orb-dark-2.CS6zkNzr_1rY58u.avif 800w, /_astro/orb-dark-2.CS6zkNzr_Z1DINPd.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/orb-dark-2.CS6zkNzr_Z25lwia.webp 320w, /_astro/orb-dark-2.CS6zkNzr_ZnTBPb.webp 480w, /_astro/orb-dark-2.CS6zkNzr_1CzIkX.webp 800w, /_astro/orb-dark-2.CS6zkNzr_Z1t8aCJ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/orb-dark-2.CS6zkNzr_OvADT.jpg" srcset="/_astro/orb-dark-2.CS6zkNzr_1szVgw.jpg 320w, /_astro/orb-dark-2.CS6zkNzr_Z1Uai5q.jpg 480w, /_astro/orb-dark-2.CS6zkNzr_6k35I.jpg 800w, /_astro/orb-dark-2.CS6zkNzr_24NhUW.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The old utilitybend about page in dark mode, showing the orb blobs behind the author's photo and bio" loading="lazy" decoding="async" fetchpriority="auto" width="1024" height="545"> </picture> <p>I wanted to keep the same idea but make them feel alive. If you haven’t played with <code>@property</code> yet, I’ve written about it <a href="https://utilitybend.com/blog/taking-a-closer-look-at-property-in-css">in depth</a> and used it for <a href="https://utilitybend.com/blog/animating-clip-paths-on-scroll-with-at-property-in-css">animating clip-paths on scroll</a>. The magic is that it lets you register custom properties with a type, which means CSS can actually interpolate them. You can’t normally transition a hue value inside an <code>oklch()</code> color, but if you register a custom property as a <code>&lt;number&gt;</code>, suddenly you can:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --atm-hue-1</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 30<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --atm-opacity-1</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 0.28<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>If you are wondering, the atm stands for atmoSPHERE, get it? get it? ok… moving on…</p> <p>The blobs themselves are <code>::before</code> and <code>::after</code> pseudo-elements with organic <code>border-radius</code> values, colored with <code>oklch</code> using those registered properties:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.homepage-sections::before</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 50vw<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 50vw<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 62% 38% 46% 54% / 60% 44% 56% 40%<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>85% 0.08 <span class="token function">var</span><span class="token punctuation">(</span>--atm-hue-1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--atm-opacity-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token function">blur</span><span class="token punctuation">(</span><span class="token function">clamp</span><span class="token punctuation">(</span>60px<span class="token punctuation">,</span> 8vw<span class="token punctuation">,</span> 120px<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>When <a href="https://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css">scroll-driven animations</a> are supported, those properties animate as you scroll down the page. The hue shifts, the opacity pulses, the blobs drift across the screen. It’s subtle, but it makes the homepage feel like it’s breathing:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.homepage-sections::before</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> atm-light-1 linear forwards<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>It took a bit of time to get it the way I wanted it, especially in the light theme I had to tinker quite a lot to keep the sort of “elegance” in the cream coor scheme.</p> <p>I used the same <code>@property</code> approach for the buttons. The gradient angle and color stops are registered custom properties, so hovering a button smoothly transitions the gradient direction and colors instead of just snapping between states. I wrote about <a href="https://utilitybend.com/blog/the-button-case-using-custom-properties-for-a-smart-button-system">building a smart button system with custom properties</a> before, and this takes that idea a step further.</p> <h2 id="navigation-has-and-anchor-positioning">Navigation: :has() and anchor positioning</h2> <p>I hated my old navigation, it was boring as hell so I really enjoy this one. The desktop navigation has a pill-shaped highlight that sits on the active page link. When you hover over a different nav item, the pill smoothly slides over to it. When you stop hovering, it slides back. No JavaScript involved, and it degrades perfectly to a simple static highlight.</p> <p>The trick combines two features I’ve written about quite a bit: <a href="https://utilitybend.com/blog/practical-uses-of-the-has-relational-pseudo-class"><code>:has()</code></a> (which I genuinely believe <a href="https://utilitybend.com/blog/weve-only-scratched-the-surface-with-has">we’ve only scratched the surface of</a>) and <a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">anchor positioning</a>.</p> <p>Let’s look at how it works. The active nav link gets an <code>anchor-name</code>. When another link is hovered, <code>:has()</code> detects this and moves the anchor name to the hovered link instead. A <code>::before</code> pseudo-element on the nav list is positioned using <code>position-anchor</code>, so it follows wherever the anchor goes:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">anchor-name</span><span class="token punctuation">:</span> --a<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.nav-desktop .nav-link</span> <span class="token punctuation">{</span> <span class="token selector">&amp;:not(:has(.nav-link:hover)).active</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --nav-pill<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:is(:hover, :focus-visible)</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --nav-pill<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token selector">.nav-desktop .nav-list:has(.nav-link:hover) .nav-link.active:not(:hover)</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.nav-desktop .nav-list::before</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --nav-pill<span class="token punctuation">;</span> <span class="token property">inset</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>top<span class="token punctuation">)</span> <span class="token function">anchor</span><span class="token punctuation">(</span>right<span class="token punctuation">)</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span> <span class="token function">anchor</span><span class="token punctuation">(</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 100vw<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--s-color-highlight<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> 0.2s<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Everything sits behind <code>@supports (anchor-name: --a)</code>. Without it, you simply get a static background color on the active link. Perfectly functional, just without the sliding animation.</p> <p>On mobile, the navigation uses the Popover API with <code>@starting-style</code> for a smooth slide-in animation. I wrote about both <a href="https://utilitybend.com/blog/open-ui-and-the-popover-api">popovers</a> and <a href="https://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style"><code>@starting-style</code></a> before, and it’s really satisfying to see them work together for something as common as a mobile menu.</p> <h2 id="scroll-state-queries-the-progress-bar-and-css-carousels">Scroll-state queries, the progress bar, and CSS carousels</h2> <p>If you’ve been following along, you might have read my articles on <a href="https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css">scroll-state queries when they first landed</a> and <a href="https://utilitybend.com/blog/is-it-scrolled-is-it-not-lets-find-out-with-css-container-scroll-state-queries">the more recent update with scrolled and scrollable states</a>. I’m using them in a few places on this site now, and I have to say, it feels great to use your own articles as reference material for the things you’re building.</p> <h3 id="the-header">The header</h3> <p>The header uses scroll-state queries in two ways. First, when it’s stuck to the top, it gets a more pronounced shadow:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">stuck</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.header</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 2px 12px <span class="token function">rgb</span><span class="token punctuation">(</span>58 52 42 / 0.1<span class="token punctuation">)</span><span class="token punctuation">,</span> 0 4px 24px <span class="token function">rgb</span><span class="token punctuation">(</span>58 52 42 / 0.06<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>Second, the header hides when you scroll down and reappears when you scroll back up. The <code>scrolled: top</code> and <code>scrolled: bottom</code> states handle it without any Intersection Observer or scroll event listeners:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrolled</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 0<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 function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrolled</span><span class="token punctuation">:</span> bottom<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 <span class="token function">calc</span><span class="token punctuation">(</span>-100% + <span class="token function">var</span><span class="token punctuation">(</span>--s-progress-height<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> <h3 id="the-article-progress-bar">The article progress bar</h3> <p>That <code>var(--s-progress-height)</code> in the translate? That’s the article reading progress bar peeking through at the bottom of the header. When you’re reading an article, there’s a thin gradient bar that grows from left to right as you scroll. Both the bar height and the translate offset share the same semantic token, so they stay in sync. It uses <code>:has(.article-body)</code> so it only shows up on pages that actually have an article, and <code>animation-timeline: scroll(root)</code> to tie the progress to the scroll position:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">body:has(.article-body) .header::after</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">width</span><span class="token punctuation">:</span> 100%<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>--s-progress-height<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inset-block-end</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> left<span class="token punctuation">;</span> <span class="token property">scale</span><span class="token punctuation">:</span> 0 1<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span> 90deg<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--s-color-brand-primary<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--s-color-brand-primary-muted<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> progress-grow linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> progress-grow</span> <span class="token punctuation">{</span> <span class="token selector">to</span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 1 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>It’s a nice little detail. Browsers that don’t support <code>animation-timeline</code> simply don’t show it. No harm done.</p> <h3 id="css-carousels-with-scroll-markers-and-scroll-buttons">CSS carousels with scroll markers and scroll buttons</h3> <p>One of the new features I’ve been most excited about is <a href="https://utilitybend.com/blog/love-at-first-slide-creating-a-carousel-purely-out-of-css">CSS-only carousels</a>. I’m using the <code>::scroll-button()</code> pseudo-element in two places on the homepage: the photo reel and the new Bluesky feed.</p> <p>I know there are accessibility concerns with these carousels, but It’s not making my content inaccessible, it might just not be the best practice. My website, my choice, and I’m ok with this (for this usecase).</p> <p>The Bluesky feed is a new addition to the homepage that shows my latest posts in a horizontally scrollable strip of cards. Both the feed and the photo reel share the same carousel CSS, which I put in a single <code>scroll-carousel.css</code> file. The scroll buttons are positioned using anchor positioning (another great use case for it), and scroll-state queries handle showing and hiding the gradients based on whether there’s content to scroll to:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.bluesky-feed, .photo-reel-carousel</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrollable</span><span class="token punctuation">:</span> left<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.scroll-fade-start</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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrollable</span><span class="token punctuation">:</span> right<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.scroll-fade-end</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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Without support for <code>scroll-marker-group</code>, the carousels still work as basic horizontal scroll containers with <code>scroll-snap</code>. You can still swipe through them, you just don’t get the nice prev/next buttons and dot indicators. Progressive enhancement doing its thing.</p> <h3 id="the-table-of-contents">The table of contents</h3> <p>The article table of contents uses <code>scroll-target-group: auto</code> combined with <code>:target-current</code> to automatically highlight the section you’re currently reading. And when anchor positioning is also supported, that highlight becomes an animated indicator bar that smoothly slides between items:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">scroll-target-group</span><span class="token punctuation">:</span> auto<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.article-toc ol</span> <span class="token punctuation">{</span> <span class="token property">scroll-target-group</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.article-toc a:target-current</span> <span class="token punctuation">{</span> <span class="token property">border-inline-start-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--s-color-brand-primary<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>--s-color-brand-primary<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>The TOC list itself uses <code>@container scroll-state(scrollable: top)</code> and <code>scrollable: bottom</code> to show fade gradients when there’s more content to scroll to. Small detail, but it helps with those longer articles where the TOC overflows. To be honest, this one might be a bit on the edge, I decided to keep the TOC in every browser even if they don’t have an active state. I might get back on that later on.</p> <h2 id="the-photography-corner">The photography corner</h2> <p>One thing I really wanted with this redesign is to give photography a more prominent place on the site. I’ve always loved taking pictures, and I hope to actually maintain this part a bit more in the upcoming years.</p> <p>The old homepage had a single hero image tucked away at the bottom. The new version has a horizontal photo reel right on the homepage, built with the same CSS carousel approach: <code>scroll-snap</code>, and <code>::scroll-button()</code> for navigation. There’s also a dedicated <a href="https://utilitybend.com/photography">photography page</a> with a proper grid layout, infinite scroll, and a lightbox. It finally feels like it has a proper home. The lightbox uses a native <code>&lt;dialog&gt;</code> element and I will be updating that grid from the moment we have <a href="https://webkit.org/blog/17660/introducing-css-grid-lanes/" target="_blank" rel="noreferrer noopener">grid-lanes in CSS</a>. It’s actually the only JS-heavy part of this website at the moment.</p> <h2 id="same-html-different-paint-job">Same HTML, different paint job</h2> <p>Something I’m quite happy about with this redesign: the HTML barely changed. The whole thing was primarily a CSS effort. The existing markup stayed intact, and all these new visual features, view transitions, corner shapes, scroll-driven animations, sticky header tricks, were layered on top of what was already there.</p> <p>That’s the whole point of progressive enhancement, isn’t it? You don’t rip out your DOM to add a squircle. The <code>data-vt</code> attributes for view transitions are one of the few HTML additions, and even those are just a handful of data attributes on elements that already existed.</p> <p>This approach also made the move to container queries a lot smoother. Same components, same markup, just a different type of query. Which brings me to…</p> <h2 id="slowly-moving-to-container-queries">Slowly moving to container queries</h2> <p>One last thing worth mentioning: I’m gradually replacing media queries with container queries where it makes sense. I’ve been writing about <a href="https://utilitybend.com/blog/styling-based-on-container-width-made-possible-with-css-container-queries">container queries since 2021</a>, so it was about time I used them more on my own site.</p> <p>The homepage article cards, for example, switch from a stacked layout to a side-by-side layout based on the container width, not the viewport:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.home-hero-articles .home-articles</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">.article-preview</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> 630px<span class="token punctuation">)</span></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> 110px 1fr<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 punctuation">}</span> </code></pre> <p>I haven’t migrated everything yet, but slowly, query by query, it’s getting there. The nice thing about container queries is that the same component just works, whether it’s in a full-width layout or squeezed into a sidebar.</p> <h2 id="other-things">Other things</h2> <p>I also updated my code examples just a bit, more especially the frame by just giving it a header (I just used a multi-background image for that). I changed some of the badges so that the CSS category reflects the CSS logo color. Removed X from the share options, and updated the about page a bit. While i was updating the about page I decided to make my company logo work with <a href="https://utilitybend.com/blog/css-animation-triggers-playing-animations-on-scroll-without-scrubbing-its-a-match">scroll-triggered animations</a>. this is now in the latest chrome so it suddenly worked (I added it in Canary).</p> <p>This is going to be a lot of fun: seing more browsers get these capabilities!</p> <h2 id="wrapping-up">Wrapping up</h2> <p>So… sorry for the amount of self-linking articles in this one, but I wanted to show that it is due to my own writing and experimenting with these features that I was able to build this improvement. All these things, small demos had to be done to actually understand how they work and how to use them. Call it selfish, but this article is a reminder to myself that all these little things can make me create better experiences.</p> <p>Every feature I added follows the same pattern: the basics works everywhere, and browsers that support newer things get something extra on top. No polyfills, no JavaScript workarounds, no “please use Chrome” disclaimer.</p> <p>It’s not perfect yet, but it’s a start (as in, good enough to ship). I’m happy I did this.</p> <p>Progressive enhancement isn’t just about fallbacks. It’s about layering experiences. Start with something solid, then add on top. The squircle corners, the view transition morphing, the sliding nav pill, the scroll-state header, the reading progress bar, the CSS carousels… none of these are essential for reading the content. But together, they make the experience feel a bit more alive, a bit more intentional (and fun!).</p> <p>I had a lot of fun building this, and I hope it inspires you to try something similar on your own projects. Pick one feature, wrap it in <code>@supports</code>, and see what happens. You might get hooked.</p> <p>Oh and just for fun, here’s what this site looked like in an even earlier life. This was the Tailwind version. Redesigning it was also the reason I never wanted to use Tailwind again.</p> <picture> <source srcset="/_astro/very-old.VQqt9KfM_145dsj.avif 320w, /_astro/very-old.VQqt9KfM_1m52a9.avif 480w, /_astro/very-old.VQqt9KfM_ZhwxQK.avif 800w, /_astro/very-old.VQqt9KfM_1vzPdL.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/very-old.VQqt9KfM_BEeyv.webp 320w, /_astro/very-old.VQqt9KfM_TE3gl.webp 480w, /_astro/very-old.VQqt9KfM_ZIWwKy.webp 800w, /_astro/very-old.VQqt9KfM_149QjX.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/very-old.VQqt9KfM_10xkkY.jpg" srcset="/_astro/very-old.VQqt9KfM_Z14oNkN.jpg 320w, /_astro/very-old.VQqt9KfM_ZLoYCX.jpg 480w, /_astro/very-old.VQqt9KfM_Z2q1zER.jpg 800w, /_astro/very-old.VQqt9KfM_ZBTbzl.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A very old version of the utilitybend website built with Tailwind, showing a dark blue theme with orange accents and article cards" loading="lazy" decoding="async" fetchpriority="auto" width="1024" height="495"> </picture> <p>I hope you enjoyed this little look in my brain (and love the little updates).</p>Brecht De RuyteCSS Animation Triggers: Playing animations on scroll without scrubbing. It's a match!https://utilitybend.com/blog/css-animation-triggers-playing-animations-on-scroll-without-scrubbing-its-a-match/https://utilitybend.com/blog/css-animation-triggers-playing-animations-on-scroll-without-scrubbing-its-a-match/Scroll-driven animations have been one of my favorite features to land in CSS. Being able to play animation progress to scroll positions opens up so many possibilities. But I always felt that something was missing: sometimes you don't want to scrub through an animation. Sometimes you just want the scroll position to determine when an animation plays, not how it plays, which for me was a far more common pattern in the past. Well, with scroll-triggered animation, this final piece of the puzzle is there. What's more, when you combine the two, it's a match so perfect, even Cupid would be impressed by the timing.Thu, 12 Feb 2026 00:00:00 GMT<picture> <source srcset="/_astro/visual.ec552bGt_1mWtYt.avif 375w, /_astro/visual.ec552bGt_5kwGc.avif 480w, /_astro/visual.ec552bGt_Z1kG2Cx.avif 680w, /_astro/visual.ec552bGt_peHKf.avif 800w, /_astro/visual.ec552bGt_ZoS9Ju.avif 980w, /_astro/visual.ec552bGt_ZyVx5c.avif 1024w, /_astro/visual.ec552bGt_Zqnb5c.avif 1660w, /_astro/visual.ec552bGt_Z1xDjBJ.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.ec552bGt_Z1q28fT.webp 375w, /_astro/visual.ec552bGt_2mx3fK.webp 480w, /_astro/visual.ec552bGt_VvsV1.webp 680w, /_astro/visual.ec552bGt_Z2nJTu8.webp 800w, /_astro/visual.ec552bGt_1RjlO4.webp 980w, /_astro/visual.ec552bGt_Z11mvY0.webp 1024w, /_astro/visual.ec552bGt_ZRN9Y0.webp 1660w, /_astro/visual.ec552bGt_Zefk0P.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.ec552bGt_23JutT.png" srcset="/_astro/visual.ec552bGt_1jH0lE.png 375w, /_astro/visual.ec552bGt_2533n.png 480w, /_astro/visual.ec552bGt_Z1nVwgm.png 680w, /_astro/visual.ec552bGt_lYe7q.png 800w, /_astro/visual.ec552bGt_Zs8Dnj.png 980w, /_astro/visual.ec552bGt_Z2dNANB.png 1024w, /_astro/visual.ec552bGt_Z25feNB.png 1660w, /_astro/visual.ec552bGt_Z1WBL06.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture> <p>I love scroll animations. In the past few years, I’ve written a blogpost on Valentine’s Day that has something to do with scroll, from <a href="https://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css">scroll-driven animations</a> to <a href="https://utilitybend.com/blog/love-at-first-slide-creating-a-carousel-purely-out-of-css">carousels</a>. This year I’m releasing it two days prior, but I am really excited about a new feature, and I’m happy that I can do something “scroll” again for 2026 with a new kid: <strong>CSS scroll-triggered animations</strong>.</p> <p>I’ve been having a lot of joy experimenting with this feature. It is currently available in <a href="https://www.google.com/chrome/canary/" target="_blank" rel="noreferrer noopener">Chrome Canary</a> with the experimental web platform features flag enabled. In this (edit: long) article, I’d like to go over the basics as well of giving you some insights on a big demo I created (once again… it started small, really…). I promise that I did my best to make this article as complete as possible, but I am thinking of creating a workshop out of this in the future. Feel free to contact me if you’d like this sort of thing.</p> <p>This article should cover most things scroll-trigger related, as well as some hot tips to keep your sanity when entering the depths with scroll animations.</p> <p>For those who just want the demo… here it is, or watch the video if your browser doesn’t have the support. For those who like to learn about this juicy feature (and some hot tips), read on!</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="The Tinkerer (scroll driven/trigger animation demo)" src="https://codepen.io/utilitybend/embed/preview/LEZrVXb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/LEZrVXb"> The Tinkerer (scroll driven/trigger animation demo)</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="The Tinkerer scroll driven and trigger animation demo"><source src="/_astro/the-tinkerer.dPCWP1ka.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>(The demo also has a secret message. Can you spot it? Let me know on the socials! 😀)</p> <h2 id="the-difference-between-scroll-driven-and-scroll-triggered">The difference between scroll-driven and scroll-triggered</h2> <p>I thought I’d at least put a little section in here to explain the difference with a simple example. Imagine you have a text element that should fade in when it enters the viewport.</p> <p>With <strong>scroll-driven</strong> animations, the opacity would be tied directly to scroll position. Scroll down a bit? <code>30%</code> opacity. Scroll more? <code>60%</code> opacity. The animation literally scrubs frame by frame based on where you are in the scroll.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Scroll-driven: opacity tied to scroll position */</span> <span class="token selector">.text</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> fade-in linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> entry 0% entry 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This works great for things like parallax backgrounds or progress indicators. But in a lot of cases, such as a text fading in, it feels… off. The text sort of “oozes” into existence rather than appearing with a nice, smooth transition. It’s just not snappy, and snappy can be really good.</p> <p>With <strong>scroll-triggered</strong> animations, the scroll position only determines when the animation starts. Once triggered, the animation plays at its normal duration and easing, just like any other CSS animation would.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Scroll-triggered: plays normally when triggered */</span> <span class="token selector">.text</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> fade-in 0.6s ease-out both<span class="token punctuation">;</span> <span class="token property">animation-trigger</span><span class="token punctuation">:</span> --my-trigger play-forwards play-backwards<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>There are always exceptions, and “it depends” is probably the biggest phrase in webdevelopment, but for a simple rule of thumb, I find this true for these kids of animations:</p> <blockquote> <p>Use scroll-driven for continuous effects, use triggers for discrete transitions.</p> </blockquote> <p>Here is a little demo to illustrate the difference:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-Driven vs Scroll-Triggered animations" src="https://codepen.io/utilitybend/embed/preview/qENMvad?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/qENMvad"> Scroll-Driven vs Scroll-Triggered animations</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="creating-a-first-scroll-trigger">Creating a first scroll-trigger</h2> <p>Let’s do the basic example. No tricks, no cleverness. One trigger, one animation. Here’s the HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scene<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>box<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>I animate when the trigger is in view.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The <code>.trigger</code> is just an invisible element positioned inside the section. It’s the thing we’ll “watch” for visibility. The <code>.box</code> is what actually animates.</p> <p>Now the CSS. Two new properties, and that’s it:</p> <h3 id="step-1-define-the-trigger-on-the-trigger-element">Step 1: Define the trigger on the trigger element</h3> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger</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> 30% 0<span class="token punctuation">;</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --my-trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>What’s inside this new <code>timeline-trigger</code> property?</p> <ul> <li><code>--my-trigger</code>: a name for this trigger (dashed ident, just like custom properties)</li> <li><code>view()</code>: the timeline source. This creates a view timeline based on the element’s position</li> <li><code>contain / cover</code>: the enter range / exit range</li> </ul> <h3 id="step-2-connect-the-animation-to-the-trigger">Step 2: Connect the animation to the trigger</h3> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.box</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> fade-in 0.5s ease-out both<span class="token punctuation">;</span> <span class="token property">animation-trigger</span><span class="token punctuation">:</span> --my-trigger play-forwards play-backwards<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>What’s inside this new <code>animation-trigger</code> property?:</p> <ul> <li><code>--my-trigger</code>: which trigger to listen to</li> <li><code>play-forwards</code>: what to do when the trigger activates (enter action)</li> <li><code>play-backwards</code>: what to do when it deactivates (exit action)</li> </ul> <p>That’s it. No <code>intersectionObserver</code>, no JavaScript event listeners, no <code>threshold</code> calculations that never feel quite right.</p> <p>Don’t worry about the ranges and actions, we’ll get to these in a minute.</p> <p>But in general, this is it for the first demo. When the trigger element becomes fully visible in the viewport (<code>contain</code>), the animation plays forward. When it starts to leave (<code>cover</code>), it plays in reverse. You can see this working in the “hello trigger” demo.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Basic trigger example: CSS Scroll-triggered animations" src="https://codepen.io/utilitybend/embed/preview/gbMdqyL?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/gbMdqyL"> Basic trigger example: CSS Scroll-triggered animations</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="enter-and-exit-ranges-controlling-the-timing">Enter and exit ranges: controlling the timing</h2> <p>I always found the naming of those enter and exit ranges confusing. I usually rely on visiting <a href="https://scroll-driven-animations.style/" target="_blank" rel="noreferrer noopener">scroll-driven-animations.style</a> when I need them. For those wondering, this is also why I still write: as my blog and Codepen have served me well as my own little snippets lab. And actually writing about it makes me remember it better.</p> <p>The range is where you fine-tune the trigger’s firing. The syntax is <code>enter-range / exit-range</code>, separated by a slash.</p> <p>The keywords</p> <ul> <li><code>cover</code>: from the first pixel entering the viewport to the last pixel leaving</li> <li><code>contain</code>: only while the element is fully visible</li> <li><code>entry</code>: while the element is entering (first pixel to become fully visible)</li> <li><code>exit</code>: while the element is exiting (fully visible to the last pixel)</li> </ul> <p>You can also use percentages within those ranges for fine control:</p> <p>Here’s a little example</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* active from first pixel in to last pixel out */</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> cover / cover<span class="token punctuation">;</span> <span class="token comment">/* activates and deactivates at the same boundary */</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / contain<span class="token punctuation">;</span> <span class="token comment">/* fires halfway through entry, releases halfway through exit */</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> entry 50% / exit 50%<span class="token punctuation">;</span> </code></pre> <p>In short… <code>contain / cover</code> means the animation plays when the trigger is fully in view, and only reverses once it starts leaving the viewport. That’s a pretty big active window, which I use most of the time. On the other hand, <code>contain / contain</code> is much tighter; in that case, the trigger deactivates as soon as the element isn’t fully contained anymore, which is great for when you want snappy behavior.</p> <p>If you need to use longhands (If you’d want to vary just the range per element while keeping the same trigger name), you can split things up:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger-name</span><span class="token punctuation">:</span> --my-trigger<span class="token punctuation">;</span> <span class="token property">timeline-trigger-source</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">timeline-trigger-range</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span> <span class="token property">timeline-trigger-exit-range</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I built a range demo where five identical animations use different range configurations. Same trigger, same animation, wildly different timing. Honestly, playing with these helped me understand the ranges better than reading the spec ever could. It’s not a perfect educational demo, but I had fun creating it. I think <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges/" target="_blank" rel="noreferrer noopener">the tool by Bramus is much more educational</a>. Still, it would be a shame not to share it:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-triggered animations: a little bit of fun with ranges and triggers" src="https://codepen.io/utilitybend/embed/preview/ogLPVmZ?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ogLPVmZ"> Scroll-triggered animations: a little bit of fun with ranges and triggers</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Please don’t use this demo as your go-to; there are many other options beyond what I’m showing here. Since this is a love message to tinkering, I thought it was fun to display these ranges by triggering them.</p> <h2 id="animation-actions-when-using-triggers">Animation actions when using triggers</h2> <p>The second thing I sort of introduced in the first demo, without really explaining, are the actions inside of <code>animation-trigger</code>. They do a lot more than just the options <code>play-forwards</code> and <code>play-backwards</code>. There’s a whole table of actions in the CSS Animations Level 2 spec, and they give you surprisingly fine-grained control.</p> <p>The syntax is always:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token property">animation-trigger</span><span class="token punctuation">:</span> &lt;trigger-name&gt; &lt;enter-action&gt; &lt;exit-action&gt; </code></pre> <p>Here’s what each action does:</p> <ul> <li><code>play-forwards</code> Sets playback rate to positive, then plays</li> <li><code>play-backwards</code> Sets playback rate to negative, then plays</li> <li><code>play</code> Plays the animation (at current rate)</li> <li><code>play-once</code> Plays the animation, but only from initial or paused state (ignores if already finished)</li> <li><code>pause</code> Pauses the animation (only if currently playing)</li> <li><code>reset</code> Sets progress back to 0 and pauses</li> <li><code>replay</code> Sets progress back to 0, then plays</li> <li><code>none</code> Does nothing</li> </ul> <p>The real power is in the combinations. I’m still in the progress on creating a few more demo’s that I don’t want to reveal just yet, but here are the my first takes on which combinations I’ll likely use the most</p> <ul> <li><code>play-forwards / play-backwards</code>: The classic. Plays forward when triggered, reverses when the trigger deactivates.</li> <li><code>play-forwards / none</code>: Plays forward on enter, and does absolutely nothing on exit. The animation stays wherever it ended up. Great for one-directional reveals that shouldn’t reverse when you scroll past</li> <li><code>play-forwards / reset</code>: Plays forward on enter, snaps instantly back to the beginning on exit. No smooth reverse, just a hard cut. Useful when you want a clean slate every time.</li> <li><code>play-forwards / pause</code>: This one’s fun! But I still have to find the practical use-case. It plays forward on enter, and freezes mid-animation if you exit while it’s still playing.</li> <li><code>play-once / none</code>: Plays the animation exactly once. After it finishes, the trigger is effectively ignored; scroll away and back, and nothing happens. This is great for intro animations that should only run the first time. This will probably be my go-to in many cases, having nice entries and not overly bombard the user with animations</li> <li><code>replay / none</code>: Every time the trigger activates, the animation restarts from the beginning. Even if it was still playing. This is the “eager restart” option.</li> </ul> <p>I created a little “animation actions demo” with a progress bar that fills over 2 seconds. The slow animation and tight <code>contain / contain</code> trigger range make it easy to scroll away mid-fill and actually see the difference between <code>pause</code>, <code>reset</code>, <code>none</code>, and <code>play-backwards</code>. I had a bunch of different tests in order to get a grasp on this and thought I should combine it in one single package… descriptions only get you so far… and a picture/demo is worth…</p> <p><strong>Note:</strong> I used custom properties here to fill the actions. I didn’t find a dedicated property for the actions, which would’ve been helpful in this case.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-triggered animations: actions explained" src="https://codepen.io/utilitybend/embed/preview/OPXoqjz?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/OPXoqjz"> Scroll-triggered animations: actions explained</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="going-in-depth-with-the-scroll-trigger--scroll-driven-valentine-demo">Going in-depth with the scroll-trigger + scroll-driven Valentine demo</h2> <p>Here’s the thing I’ve come to really appreciate: combining triggers with scroll-driven animations. To be honest, it probably was my first thought: “combined, this will be awesome”. Really, think about it, these kinds of things are complementary. In the same project, you might want:</p> <ul> <li>Triggers for text reveals (they should fade in with a nice ease-out, not scrub)</li> <li>Scroll-driven for background visibility (fade backgrounds in and out based on scroll position)</li> <li>Scroll-driven for parallax effects (layers moving at different speeds)</li> <li>Triggers for SVG drawing effects (a waterfall should draw itself once triggered, not scrub)</li> </ul> <p>The combination gives you the best of both worlds: precise scrollytelling for the big picture, with natural-feeling animations for the details.</p> <p>So.. I started this demo with a Valentine’s Day poem, the plan was a small demo, but that went overboard really quick. I’d love to tell you about the thought behind the poem, but feel free to skip to the next section if you just want the technical info.</p> <p>I’ve been going to quite a lot of conferences in 2025 and saw a lot of AI talks. There are always so many strong believers, and so many haters. I really hate both extremes in this case. The worst people on the “extreme pro end” are the ones who claim that handcrafting things is dead… It’s really not. Yes, for your typical agency work, this might become less and less needed. But there are still tinkerers, crafty people, innovative leaders. People who create beautiful experiences that can’t just come into existence by completely relying on a machine (it will make things faster and help… for sure, or they have to train their agents). People mostly buy furniture from IKEA, but that doesn’t mean there aren’t any crafty people who create beautiful tables, dressers, etc… Guess who designs these types of furniture to begin with? Some people just enjoy it, and I strongly believe that those people, the ones who tinker and enhance themselves with “the machines,” are the ones we’ll need if we want to grow in quality as well as quantity, the ones that have that extra “edge”. Tinkering leads to new knowledge, interesting features, and better ways. I applaud combining tinkering with AI, it’s fantastic. But we still need to grow in combining both, finding the right balance. So I thought… for those that also heard the comment or doubt themselves, “Is my handcrafting still worth it?”, well, this one is for you.</p> <p>Ok, let’s continue with the technical part 🙂</p> <h3 id="the-basic-setup-fixed-stuff--scroll-trigger--driven">The basic setup, fixed stuff + scroll trigger + driven</h3> <p>The idea is pretty straightforward: all the visual stuff (backgrounds, SVGs, illustrations) live in position: fixed layers that cover the full viewport. They don’t scroll. What does scroll is a <code>&lt;main&gt;</code> element containing the poem text. I decided to split them with <code>&lt;section&gt;</code> elements stacked vertically, creating enough height to create the scroll distance.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token comment">&lt;!-- Fixed visual layers (don&#39;t scroll) --&gt;</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">&quot;</span>fixed-layer bg-layer bg-paper<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>fixed-layer centered-layer tinkerer-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>fixed-layer bg-layer bg-mountain<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>fixed-layer waterfall-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- ...more layers... --&gt;</span> <span class="token comment">&lt;!-- Scrollable poem (this is what creates the scroll) --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>poem<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>poem-section<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>section-trigger<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--heart-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>poem-text<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger-ref</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--heart-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>To you...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- ...more sections... --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Each fixed layer fades in and out based on scroll position using <code>animation-timeline: scroll(root)</code>. So as the user scrolls through the poem, the backgrounds cross-fade behind the text. I recently watched a musical where they did the same thing… There was a stage where the backdrops changed while the actors (the text in this metaphor) walked on and off. So next up…</p> <h3 id="two-animation-systems-combined">Two animation systems, combined!</h3> <p>As I explained with my stage metaphor, I changed the backgrounds using the root scroller for that true scorllytelling experience.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.bg-mountain</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> visible linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> 16% 27%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.heart-stroke</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> heart-draw linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> 0% 4%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The poem text, on the other hand, uses scroll-triggered animations. Each section has a trigger element, and when that trigger enters the viewport, the text plays a text-reveal animation with a proper easing (no half opacity text anymore)</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.section-trigger</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --heart-trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.heart-text</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> text-reveal 0.6s ease-out both<span class="token punctuation">;</span> <span class="token property">animation-trigger</span><span class="token punctuation">:</span> --heart-trigger play-forwards play-backwards<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>The result:</strong> backgrounds scrub smoothly with your scroll, while the text pops in. I migh’ve been geeking out a bit too much on this but I dragged that scrollbar for minutes to see the satisfying difference between the two.</p> <p>Now, if you look at that last code block and imagine repeating it for all those sections… yeah, that feels like a dumb machine would write it and “it just works”, but we’re smart, so… Let’s fix that.</p> <h3 id="scaling-up-making-it-dry">Scaling up: making it DRY</h3> <p>The demo has 11 sections, each with its own trigger. Writing a separate <code>timeline-trigger</code> rule for every single one is kind of painful. Let me show you how I avoided that.</p> <p>So yes, I really started like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger-heart</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --heart-trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.trigger-cascade</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --cascade-trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.trigger-robot</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> --robot-trigger <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* ... and so on for every single trigger */</span> </code></pre> <p>This works, but it’s tedious. Every new section means a new CSS rule that’s essentially copy-paste with a different name.</p> <h4 id="the-attr-one-liner">The attr() one-liner</h4> <p>This is where the new typed <code>attr()</code> syntax in Chrome comes in. If you store the trigger name in a <code>data-trigger</code> attribute on the HTML element:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>trigger<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--heart-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>trigger<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--cascade-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>trigger<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--robot-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>…you can read it directly in CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger</span> <span class="token punctuation">{</span> <span class="token property">timeline-trigger</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-trigger <span class="token function">type</span><span class="token punctuation">(</span>&lt;custom-ident&gt;<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span> contain / cover<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>One rule for all the triggers!</strong> The <code>attr(data-trigger type(&lt;custom-ident&gt;))</code> tells the browser to read the attribute value and interpret it as a custom identifier, the exact type that <code>timeline-trigger</code> expects.</p> <p>The same trick works on the animation side. Give each animated element a <code>data-trigger-ref</code> pointing to the trigger it should listen to:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>heart-text<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-trigger-ref</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--heart-trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[data-trigger-ref]</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> text-reveal <span class="token function">var</span><span class="token punctuation">(</span>--duration-normal<span class="token punctuation">)</span> ease-out both<span class="token punctuation">;</span> <span class="token property">animation-trigger</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-trigger-ref <span class="token function">type</span><span class="token punctuation">(</span>&lt;custom-ident&gt;<span class="token punctuation">)</span><span class="token punctuation">)</span> play-forwards play-backwards<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.heart-text</span> <span class="token punctuation">{</span> <span class="token comment">/* custom reveal variables */</span> <span class="token punctuation">}</span> </code></pre> <p>Add a new section? Add a trigger element in the HTML with the right <code>data-trigger</code> value. No CSS changes needed. I love how smart we can make this by just using some modern capabilities.</p> <h3 id="reusable-keyframes-with-css-variables">Reusable keyframes with CSS variables</h3> <p>While I was creating this demo, I was starting to struggle a bit with duplicate animations, so I decided to simplify the keyframes a bit. Instead of writing separate animations for “fade from left”, “fade from right”, “fade from below”, we can use variables with fallbacks in combination with custom properties:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> text-reveal</span> <span class="token punctuation">{</span> <span class="token selector">from</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--reveal-x<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--reveal-y<span class="token punctuation">,</span> 30px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">to</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">translate</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--end-x<span class="token punctuation">,</span> 0<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--end-y<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>By default, this reveals from 30px below. But when I wanted to create an exception, I just had to update the variables:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.from-left</span> <span class="token punctuation">{</span> <span class="token property">--reveal-x</span><span class="token punctuation">:</span> -50px<span class="token punctuation">;</span> <span class="token property">--reveal-y</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.from-right</span> <span class="token punctuation">{</span> <span class="token property">--reveal-x</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span> <span class="token property">--reveal-y</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>One keyframe, infinite directions…</p> <h3 id="centralizing-your-scroll-choreography">Centralizing your “scroll choreography”</h3> <p>Do people say choreography in this sense? I dunno, might be my daughter’s dancing lessons in my head… Anyway, this one’s a hot tip for full-page scrollytelling. When you’re using <code>animation-range</code> on a bunch of scroll-driven elements, those percentage ranges end up scattered across your stylesheet. I was constantly scrolling back and forth, tweaking those percentages… Until I got sick of them and decided to pull them all into a dedicated layer:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@layer</span> scrollTimeline</span> <span class="token punctuation">{</span> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--range-hero</span><span class="token punctuation">:</span> 0% 9%<span class="token punctuation">;</span> <span class="token property">--range-intro</span><span class="token punctuation">:</span> 7% 18%<span class="token punctuation">;</span> <span class="token property">--range-feature</span><span class="token punctuation">:</span> 16% 27%<span class="token punctuation">;</span> <span class="token property">--range-detail</span><span class="token punctuation">:</span> 25% 45%<span class="token punctuation">;</span> <span class="token property">--range-outro</span><span class="token punctuation">:</span> 90% 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This way, I could play around with a sort of centralized storyboard. Overlaps are visible, retiming is a single-line change; think of it as a timeline editor built into your stylesheet.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.waterfall-container</span> <span class="token punctuation">{</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--range-hero<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.robot-container</span> <span class="token punctuation">{</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--range-intro<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h3 id="debugging-visualizing-your-triggers">Debugging: visualizing your triggers</h3> <p>When building scroll-triggered pages, it really helps to see where your triggers are. A dashed border on the trigger element goes a long way:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px dashed <span class="token function">oklch</span><span class="token punctuation">(</span>60% 0.1 180 / 0.3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.trigger::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;trigger zone&quot;</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">font-family</span><span class="token punctuation">:</span> ui-monospace<span class="token punctuation">,</span> monospace<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 0.6rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>60% 0.1 180 / 0.4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>If you’re using the <code>data-trigger</code> attribute approach, you can make the label dynamic:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger::after</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-trigger<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Each trigger gets the correct label automatically. When you’re done debugging, comment it out or hide it behind a <code>.debug</code> class. I left it in the demo; feel free to uncomment it to see it in action.</p> <p>Here is that demo once again:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="The Tinkerer (scroll driven/trigger animation demo)" src="https://codepen.io/utilitybend/embed/preview/LEZrVXb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/LEZrVXb"> The Tinkerer (scroll driven/trigger animation demo)</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="browser-support">Browser support</h2> <p>I know, I know.. This is, unfortunately, still experimental. As of writing, you need Chrome Canary with the “Experimental Web Platform features” flag enabled. The feature should arrive in Chrome 146, which might actually arrive by the end of February.</p> <p>For production, you’ll want a fallback; you can add a support query for this, but in general it’s up to you… Do you want this as a progressive enhancement, or do you have the time to create an <code>intersectionObserver</code> fallback?</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><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">animation-trigger</span><span class="token punctuation">:</span> none<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Fallback styles */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="conclusion">Conclusion</h2> <p>Animation triggers are one of those features that feel obvious. We’ve had scroll-driven animations for a while, but the ability to use scroll position as a signal rather than a driver always felt a bit broken. I’m really happy it’s here.</p> <p>The basics are simple: <code>timeline-trigger</code> on an element to define when it fires, <code>animation-trigger</code> on another element to say what should happen. But the depth is there when you need it: action pairs for fine-tuning control, longhand properties for per-element ranges, and the <code>attr()</code> capability really comes in handy here for scaling to dozens of triggers. I do believe an extra property to target actions individually would’ve been handy for one of my demos, but not sure if I’d miss it in a real-world situation, that part is for me to discover at a later stage.</p> <p><strong>Further reading:</strong></p> <ul> <li><a href="https://drafts.csswg.org/css-animations-2/" target="_blank" rel="noreferrer noopener">CSS Animations Level 2 spec</a>.</li> <li> <a href="https://developer.chrome.com/blog/scroll-triggered-animations" target="_blank" rel="noreferrer noopener">CSS scroll-triggered animations are coming!</a> </li> </ul> <p>Happy Valentine’s Day and happy triggering</p>Brecht De RuyteIs it scrolled? Is it not? Let's find out with CSS @container scroll-state() querieshttps://utilitybend.com/blog/is-it-scrolled-is-it-not-lets-find-out-with-css-container-scroll-state-queries/https://utilitybend.com/blog/is-it-scrolled-is-it-not-lets-find-out-with-css-container-scroll-state-queries/Oh, how I have been waiting to write this title! If you've read a few things by me, you know I'm always a bit hyped when CSS gives us the keys to the state-machine kingdom. For years, we've relied on intersection observers or scroll events in JavaScript to answer simple questions about an element's position and state. We already have a lot of these things happening in browsers, such as scroll-driven animations. But more state information is on the rise with an update on scroll-state queries.Fri, 23 Jan 2026 00:00:00 GMT<picture> <source srcset="/_astro/visual.BsG_KDY__Z1ebzKY.avif 375w, /_astro/visual.BsG_KDY__Z2vNx4g.avif 480w, /_astro/visual.BsG_KDY__18m1pV.avif 680w, /_astro/visual.BsG_KDY__Z2bTm0d.avif 800w, /_astro/visual.BsG_KDY__249TiY.avif 980w, /_astro/visual.BsG_KDY__Z5mDAt.avif 1024w, /_astro/visual.BsG_KDY__3bHow.avif 1660w, /_astro/visual.BsG_KDY__Z144q81.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.BsG_KDY__130UMz.webp 375w, /_astro/visual.BsG_KDY__ZeB1uH.webp 480w, /_astro/visual.BsG_KDY__Z1ECAOr.webp 680w, /_astro/visual.BsG_KDY__5i9yl.webp 800w, /_astro/visual.BsG_KDY__ZIOHVo.webp 980w, /_astro/visual.BsG_KDY__ZwMCuh.webp 1024w, /_astro/visual.BsG_KDY__Zoeguh.webp 1660w, /_astro/visual.BsG_KDY__fjysS.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.BsG_KDY__ZvRO74.jpg" srcset="/_astro/visual.BsG_KDY__Z9lBL8.jpg 375w, /_astro/visual.BsG_KDY__Z1qXz4p.jpg 480w, /_astro/visual.BsG_KDY__2dbYpM.jpg 680w, /_astro/visual.BsG_KDY__Z174o0m.jpg 800w, /_astro/visual.BsG_KDY__Z1Vcgv6.jpg 980w, /_astro/visual.BsG_KDY__Z2dQFoA.jpg 1024w, /_astro/visual.BsG_KDY__Z25ijoA.jpg 1660w, /_astro/visual.BsG_KDY__Z1gJsrq.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture> <p>I don’t know about you, but for me, Intersection Observer always felt like this magical thing that kind of works, but somehow I never got it to work exactly the way I wanted it. It missed control, it missed clarity. Way too many times have I been playing with margins and thresholds in that API only to notice that it didn’t always work perfectly on every screen.</p> <p>If you are in the same boat as me, well, it seems that the future is bright, because <code>@container scroll-state</code> is here to make our lives a whole lot easier. For the sake of this article, I’m going to refer to them as just “Scroll-state queries”, ditching the container part.</p> <p>There are a few updates in <strong>Chrome 144</strong>, but a few things were already available in <strong>Chrome 133</strong>, so let’s recap this first.</p> <h2 id="a-quick-recap-how-do-scroll-state-queries-work">A Quick Recap: How Do Scroll-State Queries Work?</h2> <p>Before we get to the shiny new toys, let’s do a little refreshing. Scroll-state queries allow a container to query its own scroll state and style its children accordingly. You simply define a container and then use a container query to check its state. It’s beautifully simple.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.scroll-ancestor</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">stuck</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.child-of-scroll-parent</span> <span class="token punctuation">{</span> <span class="token comment">/* Magic happens here! */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This functionality landed in <strong>Chrome 133</strong> with a trio of incredibly useful states that already solved some major headaches.</p> <h3 id="the-power-trio-stuck-snapped-and-scrollable">The power trio: stuck, snapped, and scrollable</h3> <p>As I played around during my first look at this feature (at the time still in Canary), “<a href="https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css">Is the sticky thing stuck?</a>”, the initial implementation gave us some powerful tools.</p> <h4 id="is-it-stuck">Is it stuck?</h4> <p>This query answers the age-old question: “Is my <code>position: sticky</code> header actually stuck to the top right now?”. Before, this required tricky JavaScript. With this CSS feature, it’s trivial. In our demo, we use a wrapper to detect the state and style the header inside it, adding a background and shadow only when it’s “stuck”.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.sticky-header-wrapper</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token property">inset-block-start</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">stuck</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.main-header</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>--color-header-bg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> 0 4px 15px <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.2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Scroll-state: Is the sticky header stuck?" src="https://codepen.io/utilitybend/embed/preview/zxBwmag?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/zxBwmag"> CSS Scroll-state: Is the sticky header stuck?</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h4 id="is-it-snapped">Is it snapped?</h4> <p>For scroll-snap galleries, we often want to highlight the active item. The <code>snapped</code> state lets us do just that. It checks if an element within a scroll-snap container is the one currently “snapped” in the viewport. In this demo, I used this to create a zoom effect on the active image, and also changed a bit of the background color.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.horizontal-track li</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">snapped</span><span class="token punctuation">:</span> inline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card-content 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.1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token function">sepia</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> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Scroll-state: Is the snap item snapped?" src="https://codepen.io/utilitybend/embed/preview/zxBwJWj?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/zxBwJWj"> CSS Scroll-state: Is the snap item snapped?</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I’m also re-entering my Pokémon demo again… I just spent way too many hours on it, not to repeat it.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-state query to check which item is snapped with CSS, Pokemon version" src="https://codepen.io/utilitybend/embed/preview/MWMZoqp?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/MWMZoqp"> Scroll-state query to check which item is snapped with CSS, Pokemon version</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h4 id="is-it-scrollable">Is it scrollable?</h4> <p>I’m sure that this one will have quite a few use cases. The <code>scrollable</code> state doesn’t care what the user has done; it cares about what the user <em>can</em> do. It asks, “Is there un-scrolled content past a certain edge?” This is perfect for showing arrows only when there’s somewhere to scroll, a task that used to require a heap of JS checking <code>scrollLeft</code>, <code>scrollWidth</code>, and <code>clientWidth</code>.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Show the LEFT arrow ONLY if there is content to scroll to on the left */</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrollable</span><span class="token punctuation">:</span> left<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.scroll-arrow.left</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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/* Show the RIGHT arrow ONLY if there is content to scroll to on the right */</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrollable</span><span class="token punctuation">:</span> right<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.scroll-arrow.right</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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Scroll-state: Is it scrollable?" src="https://codepen.io/utilitybend/embed/preview/ZYOKRXV?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZYOKRXV"> CSS Scroll-state: Is it scrollable?</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p><strong>Note:</strong> all data/horses in this table were created by using AI; anything resembling real horses or people is purely accidental. If your horse feels offended, you can always let me know, and I will remove it from this generated list. (I mean… at least I’m honest about it…)</p> <p>This is a very specific use case, but you could, for example, check if a container is scrollable in general using the <code>inline</code> or <code>block</code> axis as a keyword.</p> <h2 id="the-new-kid-container-scroll-statescrolled-arrives-in-chrome-144">The new kid: @container scroll-state(scrolled) arrives in Chrome 144!</h2> <p>That original trio was powerful, but one piece of the puzzle was missing: understanding the action of scrolling itself. With <strong>Chrome 144</strong>, the arrival of the <code>scrolled</code> state completes the picture, and it will help us with a very common pattern across the web.</p> <p>The <code>scrolled</code> state is all about the user’s immediate action. It tracks the direction of the most recent scroll. Think of it as asking the browser, “Which way did the user just move?”</p> <p>This is perfect for the classic “hidey-bar” header.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* If the last scroll was DOWN, hide the header */</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrolled</span><span class="token punctuation">:</span> bottom<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.main-header</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> <span class="token punctuation">}</span> <span class="token comment">/* If the last scroll was UP, show the header */</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrolled</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.main-header</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> <span class="token punctuation">}</span> </code></pre> <p>No more JavaScript janky headers. Yay!</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Scroll-state: Has the user scrolled up or down?" src="https://codepen.io/utilitybend/embed/preview/ZYOKxyb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZYOKxyb"> CSS Scroll-state: Has the user scrolled up or down?</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>The <code>scrolled</code> query can also be used for a one-time check. By querying <code>scrolled: inline</code>, we can ask, “Has this container been scrolled horizontally at all?” This is ideal for a scroll hint that should disappear after the user’s first interaction.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">scrolled</span><span class="token punctuation">:</span> inline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.scroll-indicator</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Scroll-state: Has the user scrolled inline?" src="https://codepen.io/utilitybend/embed/preview/QwEvrdE?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/QwEvrdE"> CSS Scroll-state: Has the user scrolled inline?</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Now I know that the preference for this sort of behavior will probably be the earlier “arrow demo”, still, it’s nice to have the option. I would like to note that there is still a <a href="https://issues.chromium.org/issues/477300992" target="_blank" rel="noreferrer noopener">small Chromium bug related to this new feature</a>, but I already filed it.</p> <h2 id="the-future-is-state-ful">The Future is State-ful</h2> <p>With the arrival of <code>scrolled</code>, the scroll-state query family feels complete. This is a clever CSS-native toolkit for handling UI changes that were once the exclusive domain of JavaScript. That being said, these are features that people will probably only start to rely on fully when there is more browser support. Even though you can perfectly use all of them as a progressive enhancement, in my experience, clients usually want “hidey headers” or “scroll indicators” in every browser. You could do a JS fallback, but you know… time and money and time and….</p> <p>Once again, I’m happy to welcome more CSS into the state-machine kingdom. These features are important for performance, UI enhancement, and yes, maybe even accessibility.</p> <h2 id="further-reading">Further reading</h2> <ul> <li><a href="https://una.im/scroll-state-scrolled" target="_blank" rel="noreferrer noopener">Directional CSS with scroll-state(scrolled)</a> by Una Kravets</li> <li><a href="https://developer.chrome.com/blog/css-scroll-state-queries" target="_blank" rel="noreferrer noopener">CSS scroll-state()</a> on Chrome for Developers</li> <li><a href="https://www.bram.us/2025/10/22/solved-by-css-scroll-state-queries-hide-a-header-when-scrolling-down-show-it-again-when-scrolling-up/" target="_blank" rel="noreferrer noopener">Solved by CSS Scroll State Queries: hide a header when scrolling down, show it again when scrolling up</a> by Bramus</li> </ul>Brecht De RuyteAn update on customizable selects: the multiple selecthttps://utilitybend.com/blog/an-update-on-customizable-selects-the-multiple-select/https://utilitybend.com/blog/an-update-on-customizable-selects-the-multiple-select/A few weeks ago, I didn't think I'd start the year with another demo on customizable select. To be honest, I was hoping to let it go for a little while. But there is an update on the rise that needs to be talked about: The multiple select. This little attribute could change a lot of things in making multiple selections; it could turn a series of checkboxes into one element, and it could improve accessibility. This is still in development, but we can take a peek in Chrome Canary.Thu, 15 Jan 2026 00:00:00 GMT<picture> <source srcset="/_astro/visual.DKxsdX2p_1LMRqx.avif 375w, /_astro/visual.DKxsdX2p_uaU8g.avif 480w, /_astro/visual.DKxsdX2p_ZUPEbt.avif 680w, /_astro/visual.DKxsdX2p_O56cj.avif 800w, /_astro/visual.DKxsdX2p_Z2Liq.avif 980w, /_astro/visual.DKxsdX2p_1RL51X.avif 1024w, /_astro/visual.DKxsdX2p_21kr1X.avif 1660w, /_astro/visual.DKxsdX2p_T4iuq.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.DKxsdX2p_Z11bJNP.webp 375w, /_astro/visual.DKxsdX2p_Z2iNH77.webp 480w, /_astro/visual.DKxsdX2p_1llQn5.webp 680w, /_astro/visual.DKxsdX2p_Z1XTw34.webp 800w, /_astro/visual.DKxsdX2p_2h9Jg8.webp 980w, /_astro/visual.DKxsdX2p_1ql68a.webp 1024w, /_astro/visual.DKxsdX2p_1yTs8a.webp 1660w, /_astro/visual.DKxsdX2p_2dsi6k.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.DKxsdX2p_ZyJ1cR.png" srcset="/_astro/visual.DKxsdX2p_1IxnMI.png 375w, /_astro/visual.DKxsdX2p_qUqur.png 480w, /_astro/visual.DKxsdX2p_ZY68Oi.png 680w, /_astro/visual.DKxsdX2p_KOByu.png 800w, /_astro/visual.DKxsdX2p_Z3ifVf.png 980w, /_astro/visual.DKxsdX2p_dT1iy.png 1024w, /_astro/visual.DKxsdX2p_msniy.png 1660w, /_astro/visual.DKxsdX2p_u5Q74.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><p>In this article, I’m not going to repeat the whole <a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">process of creating a customizable select</a>, and I’m not adding this to the part of the series I wrote earlier on this great feature. But I want to take a look at the work that is being done when you add the attribute <code>multiple</code> to the select element.</p> <h2 id="the-multiple-select">The multiple select</h2> <p>Who uses this element? Let’s be honest that usage on the web is scarce. It’s not very intuitive to operate with a mouse, actually, it’s probably more intuitive with a keyboard in this case? It’s a strange element. I actually showed it to a few of my colleagues; you would be amazed at how many people don’t know that you have to hold <kbd>ctrl</kbd>/<kbd>cmd</kbd> to make a selection. In my experience, I always rely on checkboxes to create a multi-selection instead of using this dedicated element.</p> <p>Making this element customizable should, in my opinion, challenge the original design, while still making sense for keyboard users and assistive technology. Different, but better, could be the way to go here.</p> <p>And that is exactly what it is starting to look like at this moment. Please note that this is still under development, and many aspects are still under active discussion.</p> <p>For this first demo, I created a music picker with the ’90s and blues as selected options.</p> <p>This is what this element looks like at the moment:</p> <picture> <source srcset="/_astro/multiselect-base.C0j7s1Ek_1mPfdJ.avif 320w, /_astro/multiselect-base.C0j7s1Ek_RHTMm.avif 480w, /_astro/multiselect-base.C0j7s1Ek_Z2lvmWU.avif 800w, /_astro/multiselect-base.C0j7s1Ek_Z2lAjOa.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/multiselect-base.C0j7s1Ek_Z20EX6A.webp 320w, /_astro/multiselect-base.C0j7s1Ek_Z2uMiwX.webp 480w, /_astro/multiselect-base.C0j7s1Ek_ZDOrtj.webp 800w, /_astro/multiselect-base.C0j7s1Ek_ZDToky.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/multiselect-base.C0j7s1Ek_Zi0dfY.png" srcset="/_astro/multiselect-base.C0j7s1Ek_J5Ggc.png 320w, /_astro/multiselect-base.C0j7s1Ek_eXlOO.png 480w, /_astro/multiselect-base.C0j7s1Ek_25VcSt.png 800w, /_astro/multiselect-base.C0j7s1Ek_25Qg2e.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A default multiple select element showing music genres with 90s and blues selected" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Now, when we turn this into a customizable select with:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</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">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>We see the following:</p> <picture> <source srcset="/_astro/custom-multiselect.taiWk76D_ZK2elP.avif 320w, /_astro/custom-multiselect.taiWk76D_Z19QS31.avif 480w, /_astro/custom-multiselect.taiWk76D_2eCT4k.avif 800w, /_astro/custom-multiselect.taiWk76D_Z1THWou.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/custom-multiselect.taiWk76D_nyei7.webp 320w, /_astro/custom-multiselect.taiWk76D_Z1gpo4.webp 480w, /_astro/custom-multiselect.taiWk76D_Z1GWL5E.webp 800w, /_astro/custom-multiselect.taiWk76D_ZL7tJx.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/custom-multiselect.taiWk76D_Z2uam2B.png" srcset="/_astro/custom-multiselect.taiWk76D_ZVPSnA.png 320w, /_astro/custom-multiselect.taiWk76D_Z1lFx4L.png 480w, /_astro/custom-multiselect.taiWk76D_22Of2z.png 800w, /_astro/custom-multiselect.taiWk76D_Z26wBqf.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A customizable multiple select element with checkmarks next to selected options" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="but-more-is-changing-than-just-looks">But more is changing than just looks,…</h3> <p>There are some navigational differences, for example:</p> <h4 id="mouse-navigation">Mouse navigation</h4> <p>When using the mouse on the original design, you had to hold <kbd>ctrl</kbd>/<kbd>cmd</kbd> while clicking to select multiple options. <strong>This now turns to a single click.</strong></p> <p>Now… personally, I am happy with that; it makes the element way more intuitive than before. Just as I did, I’d encourage you to test this with your friends by giving them a normal multiple-select and letting them select multiple options; you’ll be amazed at how many would fail on their first try.</p> <p>Even more… on touch devices, it was already a “single tab”.</p> <h4 id="keyboard-navigation">Keyboard navigation</h4> <p>If you want to navigate this element with a keyboard, you also need to hold the <kbd>ctrl</kbd>/<kbd>cmd</kbd> key and use your up and down arrows to go through the selection when you want to select multiple options, and you press the spacebar to confirm it. The thing I hate about that design is that when you accidentally let go of your <kbd>Ctrl</kbd> key, you lose your selection and only select one item.</p> <p>With the customizable select the need for holding <kbd>cmd</kbd>/<kbd>ctrl</kbd> is removed. But we do lose a few smaller keyboard functionalities:</p> <ul> <li><kbd>Ctrl</kbd>/<kbd>cmd</kbd> + <kbd>a</kbd>: to select them all</li> <li><kbd>Ctrl</kbd>/<kbd>cmd</kbd> + <kbd>arrow</kbd>: to select the one below right away.</li> </ul> <p>I personally feel like this is a minor loss compared to the wins. But I’ll let you be the judge of that.</p> <h2 id="the-ability-to-style-the-customizable-select">The ability to style the customizable select</h2> <p>This updated behavior allows us to style the customizable select like a proper selection. A thing that came directly to mind was a music genre selection. In a lot of cases, people like more than one music genre, so I was able to style this with just one select element:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A styled music genre multiple select with card-style options"><source src="/_astro/music-select.CTm-vUs1.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>You can check out the following demo in Chrome Canary for now:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Multiple select music selection" src="https://codepen.io/utilitybend/embed/preview/xbOOayg?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/xbOOayg"> Multiple select music selection</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>This is pretty much the gist of it:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><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">&quot;</span>genres<span class="token punctuation">&quot;</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">&quot;</span>genre-select<span class="token punctuation">&quot;</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">&quot;</span>Select multiple music genres<span class="token punctuation">&quot;</span></span> <span class="token attr-name">multiple</span><span class="token punctuation">&gt;</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">&quot;</span>options-grid<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>80s<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>icon<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>#80s<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>use</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>80s<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- other options --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And then with the extra <code>.options-grid</code> we can do the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.options-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-fill<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>160px<span class="token punctuation">,</span> 100%<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> 20px<span class="token punctuation">;</span> <span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token comment">/* Style options as a card */</span> <span class="token selector">&amp;::checkmark</span> <span class="token punctuation">{</span> <span class="token comment">/* style checkmark to top right */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="why-this-could-be-a-win-for-accessibility">Why this could be a win for accessibility</h3> <p>Apart from the questions I raised about keyboard navigation, I believe that by following the idea behind the previous demo, you can imagine this could remove a lot of the bad checkbox or button tricks in the world. Too many times have I seen places where people use checkboxes for it, and just set their checkbox to <code>display: none;</code> too many times have I seen it to be buttons with bad behavior, lost outlines, etc…</p> <p>In this example, I didn’t change anything for the outlines. I just set the picker to a grid and styled the options as cards, and it just works…</p> <p>Yes, people will always be able to break these things, and since I’m human and prone to error, I’ll probably make a bad example as well in my future career, but this does provide a solid default, and I think that’s the important thing to keep in mind here.</p> <h2 id="the-size-attribute-on-a-multiple-select-element">The size attribute on a multiple select element</h2> <p>You probably thought we were done here? Well, we’re not.</p> <p>The <code>size</code> attribute on a select is a special one. It determines how many options of the select are visible by default. For the multiple select, this is by default set to 4, and if we choose to set this to one, we get a rather peculiar look:</p> <picture> <source srcset="/_astro/size-1.BQ2ab7-d_ZVz8AJ.avif 320w, /_astro/size-1.BQ2ab7-d_z9UTK.avif 480w, /_astro/size-1.BQ2ab7-d_Z1Gm0PK.avif 800w, /_astro/size-1.BQ2ab7-d_2sYMuC.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/size-1.BQ2ab7-d_1kCmWO.webp 320w, /_astro/size-1.BQ2ab7-d_Z2dOGkC.webp 480w, /_astro/size-1.BQ2ab7-d_zPuHN.webp 800w, /_astro/size-1.BQ2ab7-d_ZjYOJK.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/size-1.BQ2ab7-d_Z29jOzX.png" srcset="/_astro/size-1.BQ2ab7-d_ZYOCey.png 320w, /_astro/size-1.BQ2ab7-d_vTrgV.png 480w, /_astro/size-1.BQ2ab7-d_Z1JButz.png 800w, /_astro/size-1.BQ2ab7-d_2pJiQN.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A multiple select with size attribute set to 1, showing a dropdown-like appearance" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Suddenly, the multiple select changes to a dropdown type of select with checkboxes inside. I find this a strange design choice that feels like it was bolted on due to the nature of the multiple select not being used often (I am not familiar with the history here).</p> <p>Now of course this should still be supported with a customizable select. When you opt-in to the feature with CSS, you get the following result:</p> <picture> <source srcset="/_astro/custom-size-1.yVyc_Sa9_1SWHxb.avif 320w, /_astro/custom-size-1.yVyc_Sa9_2rWz7n.avif 480w, /_astro/custom-size-1.yVyc_Sa9_ZnDDau.avif 800w, /_astro/custom-size-1.yVyc_Sa9_FcYNj.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/custom-size-1.yVyc_Sa9_Z1LwOnh.webp 320w, /_astro/custom-size-1.yVyc_Sa9_Z1dwWN5.webp 480w, /_astro/custom-size-1.yVyc_Sa9_112WHY.webp 800w, /_astro/custom-size-1.yVyc_Sa9_24TAGM.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/custom-size-1.yVyc_Sa9_aAYz6.png" srcset="/_astro/custom-size-1.yVyc_Sa9_Z2tdDIa.png 320w, /_astro/custom-size-1.yVyc_Sa9_Z1UdM8X.png 480w, /_astro/custom-size-1.yVyc_Sa9_jm8n6.png 800w, /_astro/custom-size-1.yVyc_Sa9_1ndLlT.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A customizable multiple select with size 1, showing consistent checkmark styling" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>I really like this idea, and I’ll tell you why. With this design, we always have the checkmarks (<code>::checkmark</code>), same as in the “non-multiple customizable select”, the “auto-sized multiselect”, and in the “<code>size=&quot;1&quot;</code> multiselect”. If there is one thing that I like about defaults, it’s consistency.</p> <blockquote> <p>Sweet sweet consistency</p> </blockquote> <p>Same as with the customizable select, we can now style this, and it can open up a lot of possibilities:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A styled reviewer select dropdown with avatars and checkmarks"><source src="/_astro/reviewer-select.hupR513I.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>You can check out the following demo in Chrome Canary:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Multiselect Chrome Canary" src="https://codepen.io/utilitybend/embed/preview/LEZZyRm?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/LEZZyRm"> Multiselect Chrome Canary</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>This system also allows me to easily switch up the design to a multiple select without a dropdown. First of all by removing the <code>size</code> attribute, removing my picker animations, and setting my <code>::picker(select)</code> to <code>height: fit-content;</code></p> <picture> <source srcset="/_astro/multiselect-reviewers-size.D68YSX8N_Z2p4rCo.avif 320w, /_astro/multiselect-reviewers-size.D68YSX8N_Z1q7J9G.avif 480w, /_astro/multiselect-reviewers-size.D68YSX8N_ZCCAmz.avif 800w, /_astro/multiselect-reviewers-size.D68YSX8N_Z2myo4H.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/multiselect-reviewers-size.D68YSX8N_Z1uYgGt.webp 320w, /_astro/multiselect-reviewers-size.D68YSX8N_Zw2ydL.webp 480w, /_astro/multiselect-reviewers-size.D68YSX8N_grzyl.webp 800w, /_astro/multiselect-reviewers-size.D68YSX8N_Z1std8M.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/multiselect-reviewers-size.D68YSX8N_Z1004Vf.png" srcset="/_astro/multiselect-reviewers-size.D68YSX8N_Z1tUmGV.png 320w, /_astro/multiselect-reviewers-size.D68YSX8N_ZuXEee.png 480w, /_astro/multiselect-reviewers-size.D68YSX8N_hvtxS.png 800w, /_astro/multiselect-reviewers-size.D68YSX8N_Z1rpj9f.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A multiple select showing reviewers without the dropdown, displaying all options inline" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Multiselect full Chrome Canary experiment" src="https://codepen.io/utilitybend/embed/preview/raLWWQy/5b31a470a25577b8ffd9b4255b0d8b7f?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/raLWWQy/5b31a470a25577b8ffd9b4255b0d8b7f"> Multiselect full Chrome Canary experiment</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>The listbox rendering mode (the fact if the select is rendered in-flow or in the page rather than with a separate button and popup by setting the size atttribute) is set to release in Chrome 145, but this is not yet for multiselect.</p> <h2 id="future-ideas-with-selectedcontent">Future ideas with selectedcontent?</h2> <p>There still isn’t anything set in stone concerning the <code>selectedcontent</code> element in combination with these examples. If you’d ask me, I’d love to see a version where with <code>size=&quot;1&quot;</code>, a <code>&lt;selectedcontent&gt;</code> would hold every option instead of falling back to “2 selected, 3 selected”.</p> <p>More specifically, I think it could be styled easily by wrapping your options with a <code>&lt;div&gt;</code>, and maybe using pseudo-elements to add commas or even just style them as little blocks.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">multiple</span> <span class="token attr-name">size</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>wrapper<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>option<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>wrapper<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>option2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>wrapper<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>option3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">selectedcontent .wrapper::after</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&#39;,&#39;</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Just sharing a thought here…</p> <p><a href="https://github.com/openui/open-ui/issues/1355" target="_blank" rel="noreferrer noopener">I opened an issue at Open UI to discuss this in the future</a>.</p> <h2 id="conclusion">Conclusion</h2> <p>I didn’t really go into too much detail on the styling of these demos because I have other articles for that. I’m planning on doing some more CSS-heavy demos in the (near) future. I did want to write about this feature because I’ve constantly told people in the past year that all these UI changes are a work in progress and that there is much more to come. This is just an update on something existing, but a very welcome feature.</p> <p>I’d like to give a reminder as well: if you are passionate about these things, if you want to test more on the “accessibility side of things” for this feature, then this might be a good time to ease into this feature and play around with it.</p> <p>With questions open, such as the <code>selectedcontent</code> element, I’m still very much excited to see what custom selects will be able to do in the future (even after all the demos I created for this feature).</p> <p>Furthermore, for you reading this, I hope you had a wonderful time with family and/or friends to start the New Year. My best wishes for 2026!</p>Brecht De RuyteCSS corner-shape, books, talks, and happy holidayshttps://utilitybend.com/blog/css-corner-shape-books-talks-and-happy-holidays/https://utilitybend.com/blog/css-corner-shape-books-talks-and-happy-holidays/An end-of-year reflection on 13 conference talks, book recommendations, struggles with AI noise, unfinished projects, personal growth, and a quick look at the exciting new CSS corner-shape property.Fri, 19 Dec 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.CJlWMcYw_Z12mqW9.avif 375w, /_astro/visual.CJlWMcYw_Z2jYofq.avif 480w, /_astro/visual.CJlWMcYw_1kbaeL.avif 680w, /_astro/visual.CJlWMcYw_Z205dbn.avif 800w, /_astro/visual.CJlWMcYw_2fY37O.avif 980w, /_astro/visual.CJlWMcYw_OTFTv.avif 1024w, /_astro/visual.CJlWMcYw_Xt2Tv.avif 1660w, /_astro/visual.CJlWMcYw_Z8M5C2.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.CJlWMcYw_1eP4Bp.webp 375w, /_astro/visual.CJlWMcYw_Z2LRFR.webp 480w, /_astro/visual.CJlWMcYw_Z1sNs0B.webp 680w, /_astro/visual.CJlWMcYw_h7inb.webp 800w, /_astro/visual.CJlWMcYw_Zx0z7y.webp 980w, /_astro/visual.CJlWMcYw_ntH0H.webp 1024w, /_astro/visual.CJlWMcYw_w340H.webp 1660w, /_astro/visual.CJlWMcYw_1aASXR.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.CJlWMcYw_Z1BApkk.png" srcset="/_astro/visual.CJlWMcYw_Z15BUzX.png 375w, /_astro/visual.CJlWMcYw_Z2neRSf.png 480w, /_astro/visual.CJlWMcYw_1gUFAW.png 680w, /_astro/visual.CJlWMcYw_Z23kGOc.png 800w, /_astro/visual.CJlWMcYw_2cIyu0.png 980w, /_astro/visual.CJlWMcYw_ZNWmNT.png 1024w, /_astro/visual.CJlWMcYw_ZFo0NT.png 1660w, /_astro/visual.CJlWMcYw_ZxKx0o.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="CSS corner-shape holiday card" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><p>It’s that time, “the most wonderful time of the year”, the time that we look back at the things we accomplished. It wasn’t all sunshine and rainbows; in fact, I think this year was once again an emotional rollercoaster, a year with ultimate highs, climbing on stages I never thought I would be on, but also some lows, struggles, unfinished projects, and re-invention.</p> <h2 id="conferencing">Conferencing</h2> <p>For this end-of-year post, I’d like to keep things brief. Those who follow me around know that I’ve spoken at 13 conferences this year. I am grateful that my company allowed me to attend. Some were on weekends, some were during my holidays, but it’s been a real pleasure speaking at so many of them and connecting with so many developers. I still haven’t figured out what exactly, but it looks like I’m doing something right. From PHP and JS conferences, to a dedicated CSS day, Frontend scoped conferences, and DevFest for big and smaller communities. It’s been an honor.</p> <p>Not sure if we can call it a conference (I did count it for the 13), but I am also grateful for being a panelist on the stage of Google I/O Connect with <a href="https://bram.us" target="_blank" rel="noreferrer noopener">Bram.us</a> and <a href="https://rachelandrew.co.uk/" target="_blank" rel="noreferrer noopener">Rachel Andrew</a>. It’s not every day you get to sit next to someone and talk about the web who actually taught you to work with Dreamweaver back in 2003 (yes, I read that book).</p> <p>Speaking of books….</p> <h2 id="reading">Reading</h2> <p>What I love about traveling is that I get to read a bit more books from time to time. I have a few recommendations. For fiction, I read “The last wish”, the book that inspired the Netflix Witcher series. I really enjoyed this, but I also played the games in the past so it was about time I read a book about it. I also read a few non-fiction books. Here are a few of them:</p> <ul> <li>Atomic Habits</li> <li>Think again</li> <li>Bounce</li> </ul> <p>But my favourite non-fiction was “<strong>Engineering Management for the rest of us</strong>”. Even tho I’m not a manager myself, this book taught me a lot, and I believe it is a must-read for anyone managing engineering teams.</p> <p>Currently, I’m reading “<strong>This is for everyone</strong>”, the biography by Tim Berners-Lee. I like it a lot and hope to finish it by New Year’s.</p> <h2 id="struggles">Struggles</h2> <p>My biggest struggles at the moment are mostly about giving things a place… AI is evolving rapidly, and I’m sick of people shoving it down everyone’s throats. It’s not that I have something against it, it’s just that there is soooo much noise that doesn’t mean anything. I can’t hear another talk about “how cool something was made by one prompt”. I hope 2026 gets a bit more real on this topic, real usecases, real problem solving, real movement. Less cool and corporate wannabes, more quality and solutions.</p> <p>And for my AI people out there who I connected with, I know it’s there, just… the noise…</p> <p>I’m going to leave the AI topic for this article right here….</p> <p>I did struggle a bit of writers block as well, and maybe I should let it go from time to time. Take time to absorb a bit more. I’m especially looking forward to playing around with some more CSS features aside from creating awesome select elements. Especially going to start the next year with <a href="https://www.bram.us/2025/12/12/css-scroll-triggered-animations-are-coming-to-chrome/" target="_blank" rel="noreferrer noopener">scroll-triggered animations</a>…</p> <p>I’ve written 19 articles this year, and I’m carefully considering writing smaller ones on a more consistent basis, maybe a few more personal ones. I’m not going to write “ranting” articles; enough people are doing that already, I’m just not that kind of person. Keeping it educational, but maybe a little bit more personal.</p> <p>Last year, I had a message. I don’t think it worked, so my wish is the same for all of us:</p> <blockquote> <p>Converse, be patient, converse some more, scream later</p> </blockquote> <p>And lastly, some lost projects… The <a href="https://github.com/CSS-Next/css-next" target="_blank" rel="noreferrer noopener">CSS Next community group</a> is currently on a pause. It just didn’t seem to catch on enough. I also noticed a lack of time on my hands for a project that didn’t really get enough traction. That is not due to the people there; I think everyone involved had the best interests and moved this project forward. I loved our Monday calls. I sure hope this is not completely the end, but for now, it is. Also my <a href="https://oklchroma.utilitybend.com/" target="_blank" rel="noreferrer noopener">Oklch color picker project</a> could use a bit of love. It’s not dead, but I’m not sure if I want to continue working on it. And I still have to fix some bugs on my <a href="https://developer.chrome.com/blog/ai-guessing-game" target="_blank" rel="noreferrer noopener">little Guess who project</a>.</p> <p>I also set off this year to write better and draw better, which actually went quite well. My journaling went with ups and downs.</p> <p>Still struggling with that weight… Conferencing didn’t help. But working on it (again).</p> <h2 id="other-good-things">Other Good things</h2> <p>So many good things in my life that I’m grateful for as well. My dad is currently cancer-free, my daughter went to the first grade (it’s crazy how fast these kids learn to read/write!), my parents got a really funny hyperactive Pomeranian dog (unfortunatly because we had to say goodbye to the last one). I got my golfing licence and won my first amateur game. There is a big stigma on this sport, unfortunately, but it’s not “elitist” everywhere. Seeing my daughter grow in character and develop hobbies is a great thing to witness. Not going to lie, it sure comes with its usual headaches, but that’s completely worth it.</p> <h2 id="so-next-year">So, next year?</h2> <p>Maybe a bit less conferencing, more time for my family, and figuring some things out career-wise. I’ve been an agency front-end developer for quite some time now. When projects are getting into trouble, I’m mostly the go-to nowaday but I do feel like I need to grow a bit in a different direction. I just don’t know in which direction and am not completely sure what is possible. I need to figure that out. I hope to grow in the non-purely-frontend-related tasks and do more for colleagues/developers close to me. We’ll see. This will be a big reflection I’ll have to make in the first half of 2026.</p> <p>Also going to keep working on my fitness, and you know, just trying to be a good person. I will keep writing, I will keep speaking, I will keep building, I will keep learning, I will keep growing. And still be writing these articles by hand, mistakes included, because I enjoy the process, I don’t care if an AI agent writes in a better tone, grammar checks are all I need.</p> <p>And for the web, well, I really hope I can get a bit more traction on this <a href="https://open-ui.org/components/enhanced-range-input.explainer/" target="_blank" rel="noreferrer noopener">multi-thumb range slider proposal</a> I’ve been working on.</p> <h2 id="css-holidays-card-and-corner-shape">CSS holidays card and corner-shape</h2> <p>Yes, this is quite enough now.</p> <p>Did you hear about this new CSS feature, corner-shape? <a href="https://chrome.dev/css-wrapped-2025/#corner-shape" target="_blank" rel="noreferrer noopener">It’s inside CSS wrapped 2025, do check it out.</a></p> <p>I do see some pixel issues depending on screen size, but I had a blast with these corner shapes to build a little holidays card out of CSS.</p> <p>For example, you can easily create a star-like shape like this now:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.star</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 2vmin<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">background</span><span class="token punctuation">:</span> #e2e8f0<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">corner-shape</span><span class="token punctuation">:</span> scoop<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Happy Holidays!</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/utilitybend/embed/preview/ZYWgReW?default-tab=" frameborder="no" loading="lazy" allowtransparency="true"><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZYWgReW"> Untitled</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div>Brecht De RuyteState, Logic, And Native Power: CSS Wrapped 2025https://utilitybend.com/blog/state-logic-native-power-css-wrapped-2025/https://utilitybend.com/blog/state-logic-native-power-css-wrapped-2025/CSS Wrapped 2025 is out! We're entering a world where CSS can increasingly handle logic, state, and complex interactions once reserved for JavaScript. It's no longer just about styling documents, but about crafting dynamic, ergonomic, and robust applications with a native toolkit more powerful than ever. Here's an unpacking of the highlights and how they connect to the broader evolution of modern CSS.Tue, 09 Dec 2025 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2025/12/state-logic-native-power-css-wrapped-2025/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteWhy Tailwind is great, why you might be missing out, and why it isn't for mehttps://utilitybend.com/blog/why-tailwind-is-great-why-you-might-be-missing-out-and-why-it-isnt-for-me/https://utilitybend.com/blog/why-tailwind-is-great-why-you-might-be-missing-out-and-why-it-isnt-for-me/People often ask me about Tailwind. I think Tailwind is great, but it isn't for me. This article is about the good stuff about Tailwind, the things you might be missing out on, and why modern CSS might be a better choice for pushing the web forward.Thu, 04 Dec 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.CzXMxC3G_ZOoMlx.avif 375w, /_astro/visual.CzXMxC3G_Z271JDO.avif 480w, /_astro/visual.CzXMxC3G_1x8NPn.avif 680w, /_astro/visual.CzXMxC3G_Z1M7yzL.avif 800w, /_astro/visual.CzXMxC3G_2sVGIq.avif 980w, /_astro/visual.CzXMxC3G_2kvEGn.avif 1024w, /_astro/visual.CzXMxC3G_2t51Gn.avif 1660w, /_astro/visual.CzXMxC3G_1lNS9P.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.CzXMxC3G_1rMId1.webp 375w, /_astro/visual.CzXMxC3G_aaKTJ.webp 480w, /_astro/visual.CzXMxC3G_Z1fPNp0.webp 680w, /_astro/visual.CzXMxC3G_u4VXM.webp 800w, /_astro/visual.CzXMxC3G_Zk2UvW.webp 980w, /_astro/visual.CzXMxC3G_1S5FMz.webp 1024w, /_astro/visual.CzXMxC3G_21E2Mz.webp 1660w, /_astro/visual.CzXMxC3G_Z2oYg3c.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.CzXMxC3G_1T0uaM.jpg" srcset="/_astro/visual.CzXMxC3G_fqaDj.jpg 375w, /_astro/visual.CzXMxC3G_Z12bLDX.jpg 480w, /_astro/visual.CzXMxC3G_Z2sdlXH.jpg 680w, /_astro/visual.CzXMxC3G_ZHhAzU.jpg 800w, /_astro/visual.CzXMxC3G_Z1wpt5E.jpg 980w, /_astro/visual.CzXMxC3G_c1CSg.jpg 1024w, /_astro/visual.CzXMxC3G_kzYSg.jpg 1660w, /_astro/visual.CzXMxC3G_198PPq.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Tailwind CSS logo with thinking emoji" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture><p>People often asked me, “How do I do this in Tailwind?” or even stated that I have something against Tailwind. Nothing could be further from the truth. I think Tailwind is great. I also still believe jQuery is great, and the usage will probably stay bigger than React ever will be. We need to talk more about tooling, but on an honest level. This article is not about “syntax” or having “long classes in your HTML”. This article is about the good stuff about Tailwind, but also about the bad stuff, the things you are missing out on, and why it isn’t for me.</p> <p>This is one of those articles that I’ve been thinking about for a long time. I always feel like I need to walk on eggshells to convey a clear and understandable message. That being said, I thought a long time about how to approach this topic, and I wanted to share it nonetheless. To give a bit more context, I’ve recently been working on a project using <a href="https://tailwindcss.com/" target="_blank" rel="noreferrer noopener">Tailwind</a>. I see the benefits, but it really is a step down from just writing CSS.</p> <p>Tailwind is a fantastic tool that is used by a multitude of companies, including the company I work for. I am not here to argue the benefits. I also believe that Tailwind is created by people who have an absolute passion for CSS, and that is a fantastic thing. I’ve also noticed that Tailwind, in some cases, has become a gateway drug for people to get more into writing CSS, which is great! It actually teaches people due to its good documentation, which also makes me wonder that we may not have enough clear “go to websites” purely for CSS documentation, or maybe they are not beginner-friendly enough, but I digress…</p> <p>But it does bring me to one important selling point for Tailwind:</p> <h2 id="clearer-vs-easier">Clearer vs easier?</h2> <p>Look, CSS is a simple language; it’s not easy, but the syntax is easy to remember, and that has been the case for a long time (as in forever). So my question here is: Is Tailwind actually easier than CSS?</p> <p>I believe it’s really not… It’s better documented, it might be clearer, but it’s not easier. When I’m forced to write Tailwind, I’m losing a lot of time since it is an abstraction of a language that I know by heart, for some reason, it also makes me write websites in a more old-fashioned manner, using way more breakpoints instead of just creating fluid experiences (but that might just be me not being trained enough). For me personally, Tailwind only has one huge benefit that I’ll get to later on.</p> <p>And this is where a divider takes place: Creating a grid with Tailwind is <strong>easy</strong>, and breakpoints are <strong>easy</strong>; as a matter of fact, margins and paddings are easy. But <strong>CSS is so much more</strong> nowadays than your good old margins and paddings. And you can use a lot of modern features of CSS right inside of Tailwind, but here is an example that I think of.</p> <p>It wasn’t that long ago that I created a full (big) website, and I had to write a total of 6 (sizing) media queries in the whole project. Because the web is fluid, we can do so much with so little. But to create a fluid grid in Tailwind, I believe that it gets rather painful on the eyes:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>grid grid-cols-[repeat(auto-fit,minmax(min(250px,100%),1fr))] gap-4<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>In those cases, I wonder if we aren’t just best off with writing some CSS, making it more humanly readable. But I do want to be fair in this article and state that this is subjective to me.</p> <p>My real point is that when we want to create these kinds of experiences, it is still a matter of learning how CSS works. Tailwind is simple and can be hard, just as CSS is. The question for your “basic website” is not if Tailwind can do it; it probably can.</p> <h2 id="but-welcome-to-the-age-of-ai">But! Welcome to the age of AI</h2> <p>If your design skills exist out of “basic Tailwind”, meaning, just your good old breakpoint-based classes for grids, margins, and paddings, I’ve got a newsflash for you. The average “AI chatbot” out there is really good at that as well.</p> <p><strong>So these are the questions you need to ask yourself:</strong></p> <ul> <li>Do you use Tailwind to create basic designs?</li> <li>Do you think it is still sufficient if your competitor is an average Joe in his garage with the prompting machine?</li> <li>Are you a person who creates fresh experiences, or is the “good old bootstrap” style enough?</li> </ul> <p>Do you want to create more evolved experiences where you might need to learn a bit more about CSS in general and maybe run into limitations at some point (more on that later)? When you learned those new CSS skills, was it still worth the trade-offs of an abstraction to an already perfect language?</p> <p>This is the reason why I always felt that Tailwind shouldn’t be a battle about yes and no… I think utility classes are great, but I believe the truth lies somewhere in the middle, a hybrid way to create unique experiences and make “the boring stuff” go quicker (even though I still freaking love styling buttons!).</p> <h2 id="naming-conventions-debugging--copy-pasta">Naming conventions, Debugging &amp; copy pasta</h2> <p>This is, in my opinion, the biggest selling point of Tailwind for companies. There is one way to write classnames, and that is final; no need to discuss naming conventions, property sort orders in CSS, and all that stuff. I’ll leave it at that, but it’s a really important thing.</p> <p>I don’t believe that copy-pasting is the sole benefit of Tailwind because it’s also the case in CSS. You can easily copy and paste stuff without having an idea of what is going on. But when it comes to debugging, I always found it hard to read every single class declaration in my DevTools instead of a good, nicely written CSS with multiple properties and values.</p> <h2 id="you-are-missing-out-but-maybe-thats-ok">You are missing out, but maybe that’s ok</h2> <p>It’s not a secret that I love writing CSS, and I like to be modern, pushing boundaries, trying progressive enhancements, creating smart systems, and this is where, for me, the biggest pain point of Tailwind lies.</p> <p>Features such as <code>@starting-style</code>, advanced color functions, <code>::details-content</code>, scroll-driven animations, <code>interpolate-size</code>, view transitions, customizable selects, and missing out on trigonometric functions are for me a deal breaker, as I think those can help us reduce dependencies, add progressive enhancements, and create more beautiful experiences. These things can push boundaries in creativity. Note that some of these things are possible, but they really become bloated.</p> <p>But that’s not all, with the new attribute capabilities in Chromium, new container queries, carousels, CSS Functions and CSS conditionals, etc, there are a lot of new things that we can do, there are also a bunch of new pseudo-elements for some of these features, and I know there might be a way for Tailwind to handle this somewhere in the future, I just feel ike it is being bolted on and maybe, it’s coming into that phase where it’s time to let go?</p> <p>I’ve been in this industry for quite some time, I’ve used Bootstrap in the past, and it was also one of those things I had to let go after a while, as CSS just got a lot better. That being said, people still use Bootstrap as well, and I am not going to judge any of them. If it works for you, by all means…</p> <p>I think it is purely up to you… Do you want to evolve with the language or stick to the abstraction? Maybe CSS just isn’t your thing and you want to get things styled. That is a choice that you have to make, but you already know that this is the reason why…</p> <h2 id="it-isnt-for-me-and-thats-ok-as-well">It isn’t for me, and that’s ok as well</h2> <p>I want to push the web forward, I want to push projects forward, and I love seeing the structure of my HTML clearly, as it helps me to create accessible experiences. I like to create a multibrand setup with smartly placed custom properties that do more than just color changes. I’m not afraid to use progressive enhancements, I love to know what is possible and learn the language to the best of my capabilities.</p> <p>So no, it isn’t for me. Even if I knew all these classnames by heart, I’d probably still lose time instead of gaining it, because I love tinkering. I don’t like “good enough” or “close enough”. The world is evolving with AI experiences, and I want to evolve UI experiences as well, think about performance by removing dependencies, because a lot of countries with shitty internet connections are economically booming, and every bit of performance helps.</p> <p>Is knowing CSS way more beneficial than just knowing Tailwind? Yes, of course, that is a no-brainer. An &lt;insert JS framework here&gt; developer who has a strong foundational skill in vanilla JavaScript will also have an extra edge. Knowing the foundations is so important, probably even more so in this AI era, and also, a lot of fun.</p> <h2 id="so-should-you-tailwind-or-not">So, should you Tailwind or not</h2> <p>It’s your choice. I think I’ve shown the downsides clearly here without neglecting the benefits. A colleague of mine also pointed out that NativeWind might be a selling point if you want to create both desktop and mobile applications (which <strong>often</strong> use simpler design patterns).</p> <p>In most cases, Tailwind will be enough, and it might just be the thing you need; And maybe it’s me, I like to go beyond “just enough”. It all depends on the direction you want to go in your “visual/UI programming” journey. When Tailwind launched, CSS had some missing features that made utility-first make sense, but let me be the one to tell you, in 2025, CSS is pretty damn cool.</p>Brecht De Ruyte5 CSS debugging features I want to see in Chrome DevTools - Holidays listhttps://utilitybend.com/blog/5-css-debugging-features-i-want-to-see-in-chrome-devtools-holidays-list/https://utilitybend.com/blog/5-css-debugging-features-i-want-to-see-in-chrome-devtools-holidays-list/It's the end of November, holidays are getting closer, and I thought, why not make a list? In this article, I want to highlight a few CSS debugging features I'd love to see in Chrome DevTools in the upcoming year, some of which are new and others that are long overdue.Fri, 28 Nov 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual-devtools.DSXvONBs_Z2qhMtt.avif 375w, /_astro/visual-devtools.DSXvONBs_Znee3X.avif 480w, /_astro/visual-devtools.DSXvONBs_Z2gcSag.avif 680w, /_astro/visual-devtools.DSXvONBs_Z1N8rgY.avif 800w, /_astro/visual-devtools.DSXvONBs_Z1wKFEO.avif 980w, /_astro/visual-devtools.DSXvONBs_11UCMR.avif 1024w, /_astro/visual-devtools.DSXvONBs_Z1nF9vm.avif 1660w, /_astro/visual-devtools.DSXvONBs_xRBCd.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-devtools.DSXvONBs_596ML.webp 375w, /_astro/visual-devtools.DSXvONBs_28cFdh.webp 480w, /_astro/visual-devtools.DSXvONBs_fe16Y.webp 680w, /_astro/visual-devtools.DSXvONBs_His0g.webp 800w, /_astro/visual-devtools.DSXvONBs_XFdBq.webp 980w, /_astro/visual-devtools.DSXvONBs_Z2lzzws.webp 1024w, /_astro/visual-devtools.DSXvONBs_j0KXf.webp 1660w, /_astro/visual-devtools.DSXvONBs_2obRtF.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-devtools.DSXvONBs_Zt63Yl.jpg" srcset="/_astro/visual-devtools.DSXvONBs_Z1Tkcym.jpg 375w, /_astro/visual-devtools.DSXvONBs_8IlQ9.jpg 480w, /_astro/visual-devtools.DSXvONBs_Z1Jfif9.jpg 680w, /_astro/visual-devtools.DSXvONBs_Z1haQlR.jpg 800w, /_astro/visual-devtools.DSXvONBs_Z10N5JH.jpg 980w, /_astro/visual-devtools.DSXvONBs_216rQC.jpg 1024w, /_astro/visual-devtools.DSXvONBs_ZoukrB.jpg 1660w, /_astro/visual-devtools.DSXvONBs_CbbBY.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Chrome DevTools holiday wishlist" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture><p>I’m not going to make this a long article, but I want you to know, that I could easily come up with more features… However, these are the ones that I really could’ve used a couple of times. And who knows, maybe some of these are features already exist, and and someone will point that out for me 🙂.</p> <h2 id="1-more-positioning-debugging">1. More positioning debugging.</h2> <p>For this, I’m going to split it into two; one of them is a more recent capability that we will start with, and the other is long overdue:</p> <h3 id="anchoring">Anchoring</h3> <p>Especially with anchoring being in Interop this year, I was hoping for some better anchoring tools. We already get a link from the positioned item to the anchor, but I kind of feel I need a bit more from time to time:</p> <ul> <li>I want to see which position-try fallback is currently active</li> <li>I want to see an icon next to the position property, indicating that the element is anchored; It might be handy to know at a glance.</li> </ul> <picture> <source srcset="/_astro/anchoring.w0kfjz0f_YbojF.avif 320w, /_astro/anchoring.w0kfjz0f_Zfqu10.avif 480w, /_astro/anchoring.w0kfjz0f_Z1iONcS.avif 800w, /_astro/anchoring.w0kfjz0f_Z1YdRJy.avif 906w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/anchoring.w0kfjz0f_wKppR.webp 320w, /_astro/anchoring.w0kfjz0f_ZGQsTN.webp 480w, /_astro/anchoring.w0kfjz0f_Z1KfM6G.webp 800w, /_astro/anchoring.w0kfjz0f_Z2qDQDm.webp 906w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/anchoring.w0kfjz0f_27ugRD.png" srcset="/_astro/anchoring.w0kfjz0f_ZEFEoJ.png 320w, /_astro/anchoring.w0kfjz0f_Z1TixJp.png 480w, /_astro/anchoring.w0kfjz0f_27ugRD.png 800w, /_astro/anchoring.w0kfjz0f_1r6ckX.png 906w" sizes="(max-width: 800px) 100vw, 80vw" alt="Screenshot showing DevTools with CSS anchoring tools, displaying a positioned element connected to its anchor point" loading="lazy" decoding="async" fetchpriority="auto" width="800" height="450"> </picture> <h3 id="general-positioning">General positioning</h3> <p>I always felt that we could get some more info for the positioned element.</p> <p>For example</p> <ul> <li>When I use position: absolute; within a relative container, it would be nice to get some sort of quick view to see on which container it is positioning itself to, as in some cases, the stack can get complicated.</li> <li>It would also be nice if I could see a distance measurement on my website for that item (this also counts for anchoring).</li> </ul> <h2 id="2-hide-overruled-styles-with-a-simple-toggle">2. Hide overruled styles with a simple toggle</h2> <p>When we only want to see the applied styles, we can look at the computed tab, we can even use the computed tab to jump back to the styles panel.</p> <p>However, there have been multiple occasions when some property is being set, and I quickly want to know which class it came from. Not every codebase is equally clean, and sometimes there are a whole bunch of styles that have been overwritten by specificity.</p> <p>I just want a quick toggle in my CSS panel to hide the crossed-out styles so that I can get a quick browse. Seems like an easy feature.</p> <picture> <source srcset="/_astro/crossed-out-styles.K-pRSEM3_Z5hmFk.avif 320w, /_astro/crossed-out-styles.K-pRSEM3_Zum8ln.avif 480w, /_astro/crossed-out-styles.K-pRSEM3_6jMLY.avif 800w, /_astro/crossed-out-styles.K-pRSEM3_nRaG1.avif 897w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/crossed-out-styles.K-pRSEM3_1BoxNh.webp 320w, /_astro/crossed-out-styles.K-pRSEM3_1cjM8e.webp 480w, /_astro/crossed-out-styles.K-pRSEM3_1N0IgA.webp 800w, /_astro/crossed-out-styles.K-pRSEM3_25y6aC.webp 897w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/crossed-out-styles.K-pRSEM3_ZwpKay.png" srcset="/_astro/crossed-out-styles.K-pRSEM3_ZI1UCR.png 320w, /_astro/crossed-out-styles.K-pRSEM3_Z186GiU.png 480w, /_astro/crossed-out-styles.K-pRSEM3_ZwpKay.png 800w, /_astro/crossed-out-styles.K-pRSEM3_ZeRngw.png 897w" sizes="(max-width: 800px) 100vw, 80vw" alt="DevTools CSS panel showing multiple crossed-out style declarations that have been overridden by specificity" loading="lazy" decoding="async" fetchpriority="auto" width="800" height="450"> </picture> <h2 id="3-guides-rulers-and-more-quick-looks-on-my-webpage">3. Guides, rulers, and more quick looks on my webpage</h2> <p>I’m just going to say it like it is. I want a LOT of features from <a href="https://visbug.web.app/" target="_blank" rel="noreferrer noopener">VisBug</a> baked into Chrome DevTools, especially the rulers, editing text, and quick overviews.</p> <p>We see these things all the time in design tools, and it’s such a shame that we don’t have them by default in our “website debugging tool”.</p> <p>I feel like this is such a missed opportunity to just have that baked in. While we’re at it, let’s also add some top and left rulers where we can draw lines out of, just as you would in a design tool.</p> <p>This is a small bit of text for a huge feature, but I believe this is really needed!</p> <p>Simply put: learn from VisBug.</p> <picture> <source srcset="/_astro/rulers.Ro_QD0cr_1hyJ8d.avif 320w, /_astro/rulers.Ro_QD0cr_1JunQE.avif 480w, /_astro/rulers.Ro_QD0cr_ZYYY0V.avif 800w, /_astro/rulers.Ro_QD0cr_Z1xDwNo.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/rulers.Ro_QD0cr_2v6owp.webp 320w, /_astro/rulers.Ro_QD0cr_Z27a5y5.webp 480w, /_astro/rulers.Ro_QD0cr_dwFng.webp 800w, /_astro/rulers.Ro_QD0cr_Zk6Rpc.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/rulers.Ro_QD0cr_1rYtRs.png" srcset="/_astro/rulers.Ro_QD0cr_Z1kCUMk.png 320w, /_astro/rulers.Ro_QD0cr_ZRHh3S.png 480w, /_astro/rulers.Ro_QD0cr_1rYtRs.png 800w, /_astro/rulers.Ro_QD0cr_TkV50.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="VisBug interface showing rulers, guides, and measurement tools overlaid on a webpage for precise design inspection" loading="lazy" decoding="async" fetchpriority="auto" width="800" height="450"> </picture> <h2 id="4-sticky-panels">4. Sticky panels</h2> <p>I’m going to refer to VisBug again, and I’m not sure if it’s a feature or a bug. But what I like is that when you have a panel, inspecting contrast, it gets stuck when you click.</p> <p>We have soooo many panels in DevTools: colors, shadows, easing, …</p> <p>Wouldn’t it be cool to just grab them and place them on your page?</p> <picture> <source srcset="/_astro/sticky-items.CV_9mrMG_ZcteT8.avif 320w, /_astro/sticky-items.CV_9mrMG_2u2Fhr.avif 480w, /_astro/sticky-items.CV_9mrMG_ZQsitO.avif 800w, /_astro/sticky-items.CV_9mrMG_5NPKf.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sticky-items.CV_9mrMG_Z10Lnn6.webp 320w, /_astro/sticky-items.CV_9mrMG_1FJwNt.webp 480w, /_astro/sticky-items.CV_9mrMG_Z1EKqWM.webp 800w, /_astro/sticky-items.CV_9mrMG_ZHthHI.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sticky-items.CV_9mrMG_1AFivz.png" srcset="/_astro/sticky-items.CV_9mrMG_2fEm6g.png 320w, /_astro/sticky-items.CV_9mrMG_Z80Qw6.png 480w, /_astro/sticky-items.CV_9mrMG_1AFivz.png 800w, /_astro/sticky-items.CV_9mrMG_Z2weG3i.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Concept showing DevTools panels pinned and floating over a webpage, including color picker and other inspection tools" loading="lazy" decoding="async" fetchpriority="auto" width="800" height="450"> </picture> <h2 id="5-scroll-stuff-animations-panel">5. Scroll-stuff animations panel</h2> <p>There is a lot of scrolling going on in CSS nowadays, from carousels, scroll-state queries, to scroll-driven animations. There is a <a href="https://chromewebstore.google.com/detail/scroll-driven-animations/ojihehfngalmpghicjgbfdmloiifhoce?hl=en" target="_blank" rel="noreferrer noopener">scroll-driven animations debugger</a> for DevTools, but even tho it is made by a Googler, it’s not a real DevTools feature.</p> <p>So, can we have this in DevTools by default?</p> <p>Can we add this? And maybe extend it for carousels, snaps, etc…</p> <p>Who knows, I think we need this.</p> <picture> <source srcset="/_astro/scroll-driven-animations.CTTg-Y2O_22vNSU.avif 320w, /_astro/scroll-driven-animations.CTTg-Y2O_Z1gmu1b.avif 480w, /_astro/scroll-driven-animations.CTTg-Y2O_Z1n6uxY.avif 800w, /_astro/scroll-driven-animations.CTTg-Y2O_ULJOs.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/scroll-driven-animations.CTTg-Y2O_pAf0o.webp 320w, /_astro/scroll-driven-animations.CTTg-Y2O_2bT4Te.webp 480w, /_astro/scroll-driven-animations.CTTg-Y2O_25a4mq.webp 800w, /_astro/scroll-driven-animations.CTTg-Y2O_ZG8O44.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/scroll-driven-animations.CTTg-Y2O_ZlseUf.png" srcset="/_astro/scroll-driven-animations.CTTg-Y2O_Z2124hh.png 320w, /_astro/scroll-driven-animations.CTTg-Y2O_ZeIenr.png 480w, /_astro/scroll-driven-animations.CTTg-Y2O_ZlseUf.png 800w, /_astro/scroll-driven-animations.CTTg-Y2O_1Wq0sc.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scroll-driven animations debugger extension showing timeline and animation controls for CSS scroll-driven animations" loading="lazy" decoding="async" fetchpriority="auto" width="800" height="450"> </picture> <h2 id="bonus-fast-features-and-a-clear-focus-shift-towards-ai">Bonus: Fast features and a clear focus shift towards AI</h2> <p>With new features coming to Chromium, I’m sometimes bummed out that the tooling is not ready yet. For example, I don’t see any pseudo-elements of the customizable select, I don’t see ::details-content, but then again, I do see pseudo-elements for carousels, it feels a bit messy, or is it just me? Sometimes I feel like the tooling should be part of the “this is stable” story, but then again, this could be a “shortage of developers” thing. I dunno, just putting that out there.</p> <p>I know recent DevTools updates had a lot to do with AI features. And that’s ok, I understand it, but I feel that there is so much handy stuff that can be done here.</p> <p>Sometimes I want to design in the browser, and who knows, maybe in the future, I want to ask Gemini to move this item by x pixels, so taking that quick look at current values might help as well.</p> <p>This is just one developer’s opinion, but let’s not forget all the cool stuff to be done in favor of prompting mechanisms.</p> <p>I welcome new AI features, but want my DevTools to be my actual toolbox, not my assistant.</p> <p>Call me a geezer, but when opening DevTools, it kinda should still feel like I’m running towards my garage, get my trusty toolbox, and pick the right tool for the job, and just tinker away.</p> <p>So these are just a few of those features that came to mind, and I’m sure I can think of many more, but I’m more curious to see what you would like? What is something you missed in DevTools?</p>Brecht De RuyteA native way of having more than one thumb on a range slider in HTMLhttps://utilitybend.com/blog/a-native-way-of-having-more-than-one-thumb-on-a-range-slider-in-html/https://utilitybend.com/blog/a-native-way-of-having-more-than-one-thumb-on-a-range-slider-in-html/We need to talk about multi-handle range sliders… Really, we do. They’re one of those UI elements we see on a variety of web shops indicating price ranges; we see them for selecting time-slots; we see them for filtering between a variety of lenght units. Creating these experiences is more often inaccessible than just working, and even when I found the so-called perfect library, I’d still run into limitations for styling and expanding upon it. I thought that I should do something about this. I might be way over my head, and that is why I need your help...Fri, 17 Oct 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.CTc0s5ff_1mrdXG.avif 375w, /_astro/visual.CTc0s5ff_4OgFp.avif 480w, /_astro/visual.CTc0s5ff_Z1lciDk.avif 680w, /_astro/visual.CTc0s5ff_oIrJs.avif 800w, /_astro/visual.CTc0s5ff_ZpopKh.avif 980w, /_astro/visual.CTc0s5ff_ZOOFtA.avif 1024w, /_astro/visual.CTc0s5ff_ZGgjtA.avif 1660w, /_astro/visual.CTc0s5ff_Z1Nws18.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.CTc0s5ff_Z1qxogG.webp 375w, /_astro/visual.CTc0s5ff_2m1MeX.webp 480w, /_astro/visual.CTc0s5ff_V0cUe.webp 680w, /_astro/visual.CTc0s5ff_Z2ogauU.webp 800w, /_astro/visual.CTc0s5ff_1QN5Nh.webp 980w, /_astro/visual.CTc0s5ff_Z1hfEno.webp 1024w, /_astro/visual.CTc0s5ff_Z18Gino.webp 1660w, /_astro/visual.CTc0s5ff_Zu8spe.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.CTc0s5ff_1MQm5v.png" srcset="/_astro/visual.CTc0s5ff_1jbJkR.png 375w, /_astro/visual.CTc0s5ff_1yM2A.png 480w, /_astro/visual.CTc0s5ff_Z1orMh9.png 680w, /_astro/visual.CTc0s5ff_lsX6D.png 800w, /_astro/visual.CTc0s5ff_ZsDTo6.png 980w, /_astro/visual.CTc0s5ff_Z2tGJd0.png 1024w, /_astro/visual.CTc0s5ff_Z2l8nd0.png 1660w, /_astro/visual.CTc0s5ff_Z2duTou.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><h2 id="the-proposal-rangegroup">The proposal: <code>&lt;rangegroup&gt;</code></h2> <p>In this article, I’m going to summarize the current explainer. I want you to know that this is very early stage, which is exactly why it needs noise from other developers. For those who want to take a deep dive, these are the steps you can take:</p> <ol> <li> <a href="https://open-ui.org/components/enhanced-range-input.explainer/" target="_blank" rel="noreferrer noopener">Read the full explainer</a> </li> <li><a href="https://brechtdr.github.io/enhanced-range-slider-poc/" target="_blank" rel="noreferrer noopener">Look at the prototype</a> (using a web component)</li> <li> <a href="https://forms.gle/ni7XmiMy6Eah9xHx7" target="_blank" rel="noreferrer noopener">filling out the feedback form</a> </li> </ol> <p>Note that if you have already given me feedback, I am still in the process of collecting more of it 🙂 I haven’t forgotten, and I value each bit of feedback a lot.</p> <p>So now, let me summarize. What if we could combine two <code>&lt;input type=&quot;range&quot; /&gt;</code> and each of those would become a thumb. In short, this is what that would look like in HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rangegroup</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>legend</span><span class="token punctuation">&gt;</span></span>Temperature Range<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>legend</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> Minimum Temperature <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">&quot;</span>range<span class="token punctuation">&quot;</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">&quot;</span>temp-min<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-25<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> Maximum Temperature <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">&quot;</span>range<span class="token punctuation">&quot;</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">&quot;</span>temp-max<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>15<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rangegroup</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>What exactly is happening here? A lot, and I’m sure with a bit of browser engineering magic that this could look different, but this starting point does make a few things clear regarding the direction of this API.</p> <p>Look at the <code>&lt;rangegroup&gt;</code> as sort of a <code>&lt;fieldset&gt;</code>; this <code>&lt;fieldset&gt;</code> has a <code>&lt;legend&gt;</code> which could become the general label for your duo-thumb range slider. Giving each thumb a <code>&lt;label&gt;</code> can help screen readers to identify the intent of each thumb. In complete honesty, I haven’t tried this in every piece of software, but inside the prototype, this is working rather nicely. Which is why I also did not add “crossing” of thumbs. I’m not saying it is impossible, but when a thumb has a “minimum” label attached and is suddenly passed the “maximum”, it’s weird….</p> <h2 id="a-progressive-enhancement">A progressive enhancement?</h2> <p>I would love to bring this idea to the table. Having a way where we could progressively enhance for most scenarios. By using this wrapping element there is a possibility to have two individual range sliders for browsers that don’t support <code>&lt;rangegroup&gt;</code>. So, depending on the use case, there is a chance of progressive enhancement here that I believe should at least be explored.</p> <picture> <source srcset="/_astro/base-example.gpvUds6g_1mDujO.avif 320w, /_astro/base-example.gpvUds6g_Zfa5eb.avif 480w, /_astro/base-example.gpvUds6g_Z2gxQaD.avif 800w, /_astro/base-example.gpvUds6g_CTeqB.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/base-example.gpvUds6g_yllPQ.webp 320w, /_astro/base-example.gpvUds6g_Z13sdH9.webp 480w, /_astro/base-example.gpvUds6g_20l9ak.webp 800w, /_astro/base-example.gpvUds6g_ZanT2m.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/base-example.gpvUds6g_Z24k5sh.png" srcset="/_astro/base-example.gpvUds6g_Z1fp2tI.png 320w, /_astro/base-example.gpvUds6g_2cXvLd.png 480w, /_astro/base-example.gpvUds6g_bzJOK.png 800w, /_astro/base-example.gpvUds6g_Z1Y9imV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A styled range slider with two blue handles and a purple track, it has a title on top: dual-handle base example" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <picture> <source srcset="/_astro/progressive-enhancement.Cohrf7HQ_Z2hqxbc.avif 320w, /_astro/progressive-enhancement.Cohrf7HQ_ZtS1Ji.avif 480w, /_astro/progressive-enhancement.Cohrf7HQ_Z26ToRu.avif 800w, /_astro/progressive-enhancement.Cohrf7HQ_MUgou.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/progressive-enhancement.Cohrf7HQ_2py9lW.webp 320w, /_astro/progressive-enhancement.Cohrf7HQ_ZR5t15.webp 480w, /_astro/progressive-enhancement.Cohrf7HQ_Z2u6Q9h.webp 800w, /_astro/progressive-enhancement.Cohrf7HQ_pHO7H.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/progressive-enhancement.Cohrf7HQ_1vdiel.png" srcset="/_astro/progressive-enhancement.Cohrf7HQ_Zhkzai.png 320w, /_astro/progressive-enhancement.Cohrf7HQ_1vcVgB.png 480w, /_astro/progressive-enhancement.Cohrf7HQ_Z6NqQA.png 800w, /_astro/progressive-enhancement.Cohrf7HQ_Z2haTox.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Two range sliders with one thumb with a label each. labels are minimum price and maximum price, fieldset legend is dual-handle base example" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="why-range-sliders-as-a-progressive-enhancement">Why range sliders as a progressive enhancement?</h3> <p>Why not <code>&lt;input type=&quot;number&quot; /&gt;</code>?</p> <p>I believe using <code>&lt;input type=&quot;range&quot;&gt;</code> elements better preserves the design intent. A range input is designed for selecting an approximate value within a scale, where the user is more interested in the position along the track than a precise number. This means that using <code>&lt;input type=&quot;range&quot;&gt;</code> as the fallback provides a more equitable, if not identical, user experience.</p> <h3 id="min-and-max-values">Min and max values</h3> <p>Since range inputs can have a <code>min</code> and <code>max</code> attribute, I thought it would be handy to extend capabilities by allowing constraints by the range group. This is one of those examples where progressive enhancement might not work anymore, but I don’t want to miss this opportunity at the starting point.</p> <ol> <li><strong>If a handle’s range is within the group’s range</strong> (e.g., <code>rangegroup max=&quot;100&quot;</code>, <code>input max=&quot;70&quot;</code>): The track will render from 0 to 100, but the handle will be prevented from moving past 70. This is a desirable pattern for use cases like a seek bar for a live video stream, where the total buffer is visible but the user cannot seek into the future.</li> <li><strong>If a handle’s range exceeds the group’s range</strong> (e.g., <code>rangegroup max=&quot;100&quot;</code>, <code>input max=&quot;200&quot;</code>): The handle’s movement will be <strong>clamped</strong> by the <code>&lt;rangegroup&gt;</code>’s bounds. The handle will not be able to exceed a value of 100. This ensures the component’s visual representation is the source of truth and prevents invalid states.</li> </ol> <h3 id="stepbetween">Stepbetween</h3> <p>It might be nice to define a boundry on how close thmbs can be against each other, this is where the <code>stepbetween</code> attribute would come in. It defines the minimum distance between handles in a range group.</p> <h2 id="styling-capabilities-and-datalist">Styling capabilities and datalist</h2> <p>In the current explainer, there are a few styling capabilities that I think we all want to have. My main background is in UI/UX, so I am aware that this explainer is not complete and has the focus there, but we need to start somewhere. The same pseudo-elements and classes defined in the new <a href="https://drafts.csswg.org/css-forms/" target="_blank" rel="noreferrer noopener">CSS Forms spec</a> should be there, but there are a couple of nuances/extras.</p> <p>Here is a short explanation:</p> <ul> <li><code>::slider-thumb(*)</code>: would style the thumb, between the parentheses, a number can occur for each thumb</li> <li><code>:slider-segment(*)</code>: This is a pseudo-class that styles a segment of the <code>::slider-track</code> the first one between edge and first thumb, the second between the first and second thumb, etc…</li> <li><code>::slider-tick</code>: Optional tick marks along the track for value representation. (when datalist is paired.)</li> <li><code>::slider-tick-label</code>: Labels associated with tick marks. (when datalist is paired.)</li> </ul> <p>I still have a lot of unfinished thoughts about the tick marks as well. For the moment, I’m leaning towards an idea that sort of works like CSS carousels, where the ticks would be wrapped in a <code>::slider-tick-group</code>. But as I said… Still a lot to think about</p> <p>What I do know is that <code>&lt;datalist&gt;</code> pairing should be supported.</p> <h2 id="more-than-two-thumbs">More than two thumbs?</h2> <p>I love the idea of having the capability to add more than two thumbs, which is actually part of the foundation of this API idea. Having the chance to add more range inputs for more thumbs.</p> <p>I added a few examples of how it could be used in my prototype, but this is why I need the community: <strong>Let it be heard: Do you want this? Why do you want this? Do you have examples of where you would use this?</strong></p> <p>Here is another one of my ideas: an opening hours slider:</p> <picture> <source srcset="/_astro/date-range.Buj9d07j_1clkwX.avif 320w, /_astro/date-range.Buj9d07j_Z2jtoNE.avif 480w, /_astro/date-range.Buj9d07j_Z2idCdI.avif 800w, /_astro/date-range.Buj9d07j_1sX5Ax.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/date-range.Buj9d07j_1mVXJr.webp 320w, /_astro/date-range.Buj9d07j_Z28RKBb.webp 480w, /_astro/date-range.Buj9d07j_Z27BY1f.webp 800w, /_astro/date-range.Buj9d07j_1DyIN1.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/date-range.Buj9d07j_224FaR.png" srcset="/_astro/date-range.Buj9d07j_Z1jshGl.png 320w, /_astro/date-range.Buj9d07j_eT6KX.png 480w, /_astro/date-range.Buj9d07j_g9SlT.png 800w, /_astro/date-range.Buj9d07j_Z12PwCL.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A range slider with legend: opening hours selector. it has 4 thumbs and ticks with hours at the bottom starting at 8 AM, ending at 10pm. the segments of the thumbs are styled, drawing a teal line between the first two and the last two. It looks like you can select opening hours by setting an am range and pm range" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>This would result in something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>rangegroup</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>opening-hours-range<span class="token punctuation">&quot;</span></span> <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>480<span class="token punctuation">&quot;</span></span> <span class="token attr-name">max</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1320<span class="token punctuation">&quot;</span></span> <span class="token attr-name">list</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>hours-ticks<span class="token punctuation">&quot;</span></span> <span class="token attr-name">stepbetween</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>30<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>legend</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>legend<span class="token punctuation">&quot;</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">&quot;</span>color-purple<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Opening Hours Selector<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>legend</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> AM Open <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">&quot;</span>range<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>540<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> AM Close <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">&quot;</span>range<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>720<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> PM Open <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">&quot;</span>range<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>780<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</span></span> PM Close <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">&quot;</span>range<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1020<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>rangegroup</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>datalist</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>hours-ticks<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>480<span class="token punctuation">&quot;</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>8 AM<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>540<span class="token punctuation">&quot;</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>9 AM<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- etc --&gt;</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">&quot;</span>1260<span class="token punctuation">&quot;</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>9 PM<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>1320<span class="token punctuation">&quot;</span></span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>10 PM<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>datalist</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">rangegroup::slider-segment(1), rangegroup::slider-segment(3), rangegroup::slider-segment(5)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>35% 0.02 230<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Muted track */</span> <span class="token punctuation">}</span> <span class="token selector">rangegroup::slider-segment(2), rangegroup::slider-segment(4)</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>--color-teal<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">rangegroup::slider-thumb</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-teal<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="expanding">Expanding…</h2> <p>I thought a lot about what should be the default and what should not be. I think that if we want to see the value inside the thumbs, anchoring could be used to put the values on top of the thumb. Showing live updated values could be handled by a bit of extra JS. There are quite a lot of possibilities here.</p> <p>The following examples anchor values on the slider. This is also using the same component:</p> <p>There is a lot more detail on all of this that you can find in the explainer. But this article is already becoming a bit too long…</p> <picture> <source srcset="/_astro/anchor-range.CC9DPpMt_Z2tbEBI.avif 320w, /_astro/anchor-range.CC9DPpMt_YbSDd.avif 480w, /_astro/anchor-range.CC9DPpMt_Z12bRif.avif 800w, /_astro/anchor-range.CC9DPpMt_1Rgdj0.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/anchor-range.CC9DPpMt_1MHkIf.webp 320w, /_astro/anchor-range.CC9DPpMt_aSKaf.webp 480w, /_astro/anchor-range.CC9DPpMt_Z1Pu0Ld.webp 800w, /_astro/anchor-range.CC9DPpMt_13X4P2.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/anchor-range.CC9DPpMt_ZOX6zS.png" srcset="/_astro/anchor-range.CC9DPpMt_Z133Bk.png 320w, /_astro/anchor-range.CC9DPpMt_Z1CQDak.png 480w, /_astro/anchor-range.CC9DPpMt_1pVIH9.png 800w, /_astro/anchor-range.CC9DPpMt_ZJMjux.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A range slider with two thumbs and a pinks segment between them. the thumbs have values in them 37 and 65. title of slider is floating tooltip handles." loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <picture> <source srcset="/_astro/special-range.DNfALsY__ZnFnw6.avif 320w, /_astro/special-range.DNfALsY__Z1LDtml.avif 480w, /_astro/special-range.DNfALsY__BNSs2.avif 800w, /_astro/special-range.DNfALsY__hS0jw.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/special-range.DNfALsY__ZSX9p.webp 320w, /_astro/special-range.DNfALsY__Z1oR3YE.webp 480w, /_astro/special-range.DNfALsY__YAiOI.webp 800w, /_astro/special-range.DNfALsY__EEpGd.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/special-range.DNfALsY__Z2l8FPf.png" srcset="/_astro/special-range.DNfALsY__ZmhJAU.png 320w, /_astro/special-range.DNfALsY__Z1KfPra.png 480w, /_astro/special-range.DNfALsY__Dcwnd.png 800w, /_astro/special-range.DNfALsY__jgDeH.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A range slider that separates budgets. title is Bugdet allocator. it separates groceries, rent, transport and internet in percentages" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h2 id="and-this-is-why-i-need-you">And this is why I need you</h2> <p>I know there are still a lot of open questions and things to be improved, which is exactly why this is so important now.</p> <p>I am not a browser engineer; I am a developer creating web applications. I’m not even sure if I’m an expert on anything, really, but I do love the web. I usually ask the stupid questions, which sometimes actually is the good kind of question. I am NOT going to build this API. I haven’t got the skillset. Which is why…</p> <p>I need it to be on the radar, on the radar for people who can make this happen. I need you to ask for this in your socials, write about this, anything, really… Make sure it can’t be ignored.</p> <p>All I can promise you in return is that I will keep asking the stupid questions, and will be testing the API in the future, making demo’s trying to break stuff. See me as your little helper, who might not really be qualified to work on web standards, but just really cares about it.</p> <p>This being picked up is the most important thing.</p> <p>And if you <a href="https://open-ui.org/components/enhanced-range-input.explainer/" target="_blank" rel="noreferrer noopener">read the explainer</a>, you can also help by <a href="https://forms.gle/ni7XmiMy6Eah9xHx7" target="_blank" rel="noreferrer noopener">filling out this feedback form</a>.</p> <p>Thank you 🙂</p>Brecht De RuyteBuild a guessing game with the Prompt APIhttps://utilitybend.com/blog/build-a-guessing-game-with-the-prompt-api/https://utilitybend.com/blog/build-a-guessing-game-with-the-prompt-api/A case study on how to build a guessing game with the prompt API. My game of Guess Who demonstrates how AI can be used to build thoughtful game logic, and the importance of prompt engineering to get the outcomes you expect.Fri, 10 Oct 2025 00:00:00 GMT<picture> <source srcset="/_astro/chrome-for-developers.DdGrVvyI_1qvz0l.avif 375w, /_astro/chrome-for-developers.DdGrVvyI_AaRR0.avif 480w, /_astro/chrome-for-developers.DdGrVvyI_Z1QGRNg.avif 680w, /_astro/chrome-for-developers.DdGrVvyI_1hRo5Y.avif 800w, /_astro/chrome-for-developers.DdGrVvyI_1LzggU.avif 980w, /_astro/chrome-for-developers.DdGrVvyI_Z1YL9T0.avif 1024w, /_astro/chrome-for-developers.DdGrVvyI_1Xoc4Q.avif 1660w, /_astro/chrome-for-developers.DdGrVvyI_ZgLm7z.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/chrome-for-developers.DdGrVvyI_1JLGyv.webp 375w, /_astro/chrome-for-developers.DdGrVvyI_Tr0qa.webp 480w, /_astro/chrome-for-developers.DdGrVvyI_Z1xqKf6.webp 680w, /_astro/chrome-for-developers.DdGrVvyI_1B8vE9.webp 800w, /_astro/chrome-for-developers.DdGrVvyI_25PnP5.webp 980w, /_astro/chrome-for-developers.DdGrVvyI_Z2mXBaM.webp 1024w, /_astro/chrome-for-developers.DdGrVvyI_1AbJN4.webp 1660w, /_astro/chrome-for-developers.DdGrVvyI_Z1SGV16.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/chrome-for-developers.DdGrVvyI_2oNYnG.jpg" srcset="/_astro/chrome-for-developers.DdGrVvyI_Z2hR3NT.jpg 375w, /_astro/chrome-for-developers.DdGrVvyI_1VYnQG.jpg 480w, /_astro/chrome-for-developers.DdGrVvyI_ZuSmNz.jpg 680w, /_astro/chrome-for-developers.DdGrVvyI_Z2qveIg.jpg 800w, /_astro/chrome-for-developers.DdGrVvyI_Z1VNmxk.jpg 980w, /_astro/chrome-for-developers.DdGrVvyI_ZAcyB8.jpg 1024w, /_astro/chrome-for-developers.DdGrVvyI_Z1Helrd.jpg 1660w, /_astro/chrome-for-developers.DdGrVvyI_2wcUoS.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Google for Developers logo" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture><div><a href="https://developer.chrome.com/blog/ai-guessing-game" target="_blank">Read the article at Chrome for Developers</a></div>Brecht De RuyteStyling siblings with CSS has never been easier. Experimenting with sibling-count and sibling-indexhttps://utilitybend.com/blog/styling-siblings-with-CSS-has-never-been-easier.-Experimenting-with-sibling-count-and-sibling-index/https://utilitybend.com/blog/styling-siblings-with-CSS-has-never-been-easier.-Experimenting-with-sibling-count-and-sibling-index/If I were to divide CSS evolutions into categories, then last year was probably the year that ended with animations and colors getting better; This year, the end of the year seems to be about those ease-of-life features. We had one of those not that long go with :has(), but with things such as sibling-count, sibling-index, functions, and conditionals, the way we write CSS might just change for the better once again. In this article, I want to dip my toe in sibling-index() and sibling-count(), while also carefully adding some functions in the mix.Fri, 12 Sep 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.BqckTkJX_Z7ugsy.avif 375w, /_astro/visual.BqckTkJX_Z1p7dKP.avif 480w, /_astro/visual.BqckTkJX_2f3kIm.avif 680w, /_astro/visual.BqckTkJX_Z15d2GM.avif 800w, /_astro/visual.BqckTkJX_Z1TkUcw.avif 980w, /_astro/visual.BqckTkJX_Z1hfPWN.avif 1024w, /_astro/visual.BqckTkJX_Z18GtWN.avif 1660w, /_astro/visual.BqckTkJX_Z2fWCul.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.BqckTkJX_29Hf60.webp 375w, /_astro/visual.BqckTkJX_R5hMI.webp 480w, /_astro/visual.BqckTkJX_ZxVhw1.webp 680w, /_astro/visual.BqckTkJX_1bYsQL.webp 800w, /_astro/visual.BqckTkJX_mQAm2.webp 980w, /_astro/visual.BqckTkJX_Z1IFOQB.webp 1024w, /_astro/visual.BqckTkJX_Z1A7sQB.webp 1660w, /_astro/visual.BqckTkJX_ZVyCSr.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.BqckTkJX_Z1HL1to.jpg" srcset="/_astro/visual.BqckTkJX_WkGwi.jpg 375w, /_astro/visual.BqckTkJX_ZkhfKY.jpg 480w, /_astro/visual.BqckTkJX_Z1KiP5I.jpg 680w, /_astro/visual.BqckTkJX_Zn4GV.jpg 800w, /_astro/visual.BqckTkJX_ZOuWcF.jpg 980w, /_astro/visual.BqckTkJX_1Erg31.jpg 1024w, /_astro/visual.BqckTkJX_1N0C31.jpg 1660w, /_astro/visual.BqckTkJX_Z2sCENK.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture> <p>I’ve been living a lot in “select land” with my last articles, and even tho I’m still passionate about all things <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">Open UI</a>, I really wanted to get back to some of those newer yummy CSS features. I was building a bunch of demos already, but never had the chance to do a full write-up. So, I’m playing some catch-up because the features I’m tackling today are already available in Chromium browsers. This article is about <code>sibling-count()</code> and <code>sibling-index()</code>, and some of my first ideas for this new feature. What can we use it for? Where does it make our lives easier? Let’s get to it.</p> <h2 id="so-what-is-this-about">So, what is this about?</h2> <p>I feel like I don’t need to explain it too much:</p> <ul> <li><code>sibling-index()</code> returns a number representing the position of the current element relative to all its sibling elements</li> <li><code>sibling-count()</code> returns a number representing the total number of siblings of the element on which it is used, <strong>including itself.</strong></li> </ul> <p>That’s it, the only tricky one is that <code>sibling-count()</code> also counts itself. I will be going over 4 demo’s and some extras in the end. Here are some quicklinks:</p> <ul> <li><a href="#creating-staggered-animations-using-sibling-index">Creating staggered animations using sibling-index()</a></li> <li><a href="#dynamic-color-spectrum-give-each-card-a-different-color-using-sibling-index-and-sibling-count">Dynamic color spectrum: Give each card a different color using sibling-index() and sibling-count()</a></li> <li><a href="#placing-items-in-a-circle-using-sibling-index-sibling-count-and-css-functions">Placing items in a circle using sibling-index(), sibling-count(), and @function</a></li> <li><a href="#creating-a-casino-cards-fan-effect-using-sibling-index-and-sibling-count">Creating a casino cards fan effect using sibling-index() and sibling-count()</a></li> <li><a href="#further-experiments-demos-that-didnt-make-the-article">Further experiments, demos that didn’t make the article</a></li> </ul> <h2 id="creating-staggered-animations-using-sibling-index">Creating staggered animations using sibling-index()</h2> <p>One of the things that I used in past animations was setting an inline custom property with CSS to create staggered animations.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span><span class="token operator">&gt;</span> <span class="token punctuation">{</span>cardData<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">card<span class="token punctuation">,</span> index</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div key<span class="token operator">=</span><span class="token punctuation">{</span>index<span class="token punctuation">}</span> className<span class="token operator">=</span><span class="token string">&quot;card&quot;</span> style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token string-property property">&#39;--stagger-index&#39;</span><span class="token operator">:</span> index <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token operator">&gt;</span> <span class="token operator">&lt;</span>h2<span class="token operator">&gt;</span><span class="token punctuation">{</span>card<span class="token punctuation">.</span>title<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>h2<span class="token operator">&gt;</span> <span class="token operator">&lt;</span>p<span class="token operator">&gt;</span><span class="token punctuation">{</span>card<span class="token punctuation">.</span>content<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">&gt;</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">&gt;</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">&gt;</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>And then I would handle my animation delay as follows:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> reveal 0.6s ease-out forwards<span class="token punctuation">;</span> <span class="token property">animation-delay</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>--stagger-index<span class="token punctuation">)</span> * 100ms<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>And this works like a charm as long as you’re looping over some cards. But imagine the situation where you suddenly have a call to action in the middle of the cards, then it gets a lot trickier. You’d have to start propagating the index to another component; it’s not necessarily the hardest thing to do, but a cleaner way is more than welcome.</p> <p>This is one of those things that suddenly becomes a lot easier with <code>sibling-index()</code>. Instead of just trying to get our CTA inside the loop or passing down index counts in other components, we could just use CSS!</p> <p>Here is pretty much the gist of it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.card-container &gt; *</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> reveal 0.6s ease-out forwards<span class="token punctuation">;</span> <span class="token property">animation-delay</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> * 0.1s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I love a bit of ease-of-life enhancements. It’s just nice to clean up the code a bit.</p> <p>Here is that in a little codepen:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Stagger cards using sibling-index()" src="https://codepen.io/utilitybend/embed/preview/wBKQPLr?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/wBKQPLr"> Stagger cards using sibling-index()</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="dynamic-color-spectrum-give-each-card-a-different-color-using-sibling-index-and-sibling-count">Dynamic color spectrum: Give each card a different color using sibling-index() and sibling-count()</h2> <p>Ever wanted to create a bunch of cards where each one of them has a different <code>background</code>, <code>color</code>, or <code>border-color</code>? What if you want to have a fixed starting color and want to end on a fixed color as well, giving each card an equal hue in between? Well, this just got a lot easier.</p> <p>The idea here is that we calculate a “hue” based on an element’s position in a list (<code>sibling-index()</code>). We can use this to create some sort of gradient that flows across any number of items, without ever having to write a single line of JS or add manual style tags with custom properties..</p> <p>Let’s look at the CSS right away. Now… the <code>calc()</code> function might seem a little much at first, but once we break it down, it will make sense. As a sidenote, this is one of the reasons I write about this stuff, to keep these calculations as a snippet for future reference. (I thanked my past self a few times)</p> <p>In this example, we’re going to have the background of our cards range between a <strong>180deg</strong> on the hue wheel for the first item, to a <strong>300deg</strong> for the last item. (hence the variables in the next example: <strong>180 + 120 = 300</strong>).</p> <p>Here is the gist of it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.spectrum-item</span> <span class="token punctuation">{</span> <span class="token property">--start-hue</span><span class="token punctuation">:</span> 180<span class="token punctuation">;</span> <span class="token property">--hue-range</span><span class="token punctuation">:</span> 120<span class="token punctuation">;</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span> 65% 0.35 <span class="token function">calc</span><span class="token punctuation">(</span> <span class="token function">var</span><span class="token punctuation">(</span>--start-hue<span class="token punctuation">)</span> + <span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--hue-range<span class="token punctuation">)</span> / <span class="token punctuation">(</span><span class="token function">sibling-count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> - 1<span class="token punctuation">)</span><span class="token punctuation">)</span> * <span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> - 1<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>The formula to achieve this is the following:</p> <p><code>start + (range / total_steps) * current_step</code></p> <p>The hard part here was figuring out how far along the color spectrum each item should be, as we want to start and end on the same color and want everything equally spaced on the hue wheel.</p> <p>The first step is to determine the size of each “step” in our color transition. To do that, we need to know how many steps there are in total. That’s where <code>sibling-count() - 1</code> comes in. If you have 5 items, there are only 4 steps between them.</p> <p>Next up, we divide our “color journey” (the <code>--hue-range</code> of 120 degrees) by the number of steps we just calculated. This gives us our “hue increment,” aka, the exact amount the color should change from one item to the next.</p> <p>Finally, we need to know which step we’re currently on. That’s where <code>sibling-index() - 1</code> comes in. We subtract one because we want our calculation to start from zero. The first item is 0 steps away from the start color, the second is 1 step away, and so on.</p> <p>In my demo, it starts with 5 items. So in that example that would mean the following:</p> <p>For five items, the hue amount for each step is 30: <strong>120 / (5 - 1)</strong>.</p> <ul> <li>Item 1: The hue is <strong>180 + (30 * 0)</strong>, which equals <strong>180</strong> (our start color).</li> <li>Item 2: The hue is <strong>180 + (30 * 1)</strong>, which equals <strong>210</strong>.</li> <li>Item 5 (the last one): The hue is <strong>180 + (30 * 4)</strong>, which equals <strong>300</strong> (our end color).</li> </ul> <p>Here is a little sandbox of the idea:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Dynamic color spectrum with oklch, sibling-index and sibling-count" src="https://codepen.io/utilitybend/embed/preview/PwPxePK?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/PwPxePK"> Dynamic color spectrum with oklch, sibling-index and sibling-count</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="placing-items-in-a-circle-using-sibling-index-sibling-count-and-css-functions">Placing items in a circle using sibling-index, sibling-count, and CSS functions</h2> <p>Staggered animations and color spectrums are cool and all… But what if we want to fundamentally change the layout of our elements?</p> <p>I’m talking about arranging items in a perfect circle, a task that can be a real pain in the… I already <a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">played with this in another article about select elements</a>. But this time I wanted to start fresh. Before we continue, here is the demo:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="The circle using sibling-index , sibling-count and functions" src="https://codepen.io/utilitybend/embed/preview/VYvVXLN?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/VYvVXLN"> The circle using sibling-index , sibling-count and functions</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>To create this behavior, you’d typically have to calculate every single position manually in JavaScript or use a pre-processor with complex loops.</p> <p>With the combination of <code>sibling-index()</code> and <code>sibling-count()</code>, we can bring some trigonometry directly into our stylesheet to hack on some perfect circle placement. I’m also throwing the new CSS <code>@function</code> in the mix, just because I can, and I really love this feature.</p> <p>First, let’s look at the custom functions that do the heavy lifting.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@function</span> <span class="token function">--pos-x</span><span class="token punctuation">(</span>--index<span class="token punctuation">,</span> --count<span class="token punctuation">,</span> --radius<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">result</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>--radius<span class="token punctuation">)</span> * <span class="token function">cos</span><span class="token punctuation">(</span>360deg / <span class="token function">var</span><span class="token punctuation">(</span>--count<span class="token punctuation">)</span> * <span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--index<span class="token punctuation">)</span> - 1<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 atrule"><span class="token rule">@function</span> <span class="token function">--pos-y</span><span class="token punctuation">(</span>--index<span class="token punctuation">,</span> --count<span class="token punctuation">,</span> --radius<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">result</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>--radius<span class="token punctuation">)</span> * <span class="token function">sin</span><span class="token punctuation">(</span>360deg / <span class="token function">var</span><span class="token punctuation">(</span>--count<span class="token punctuation">)</span> * <span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--index<span class="token punctuation">)</span> - 1<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>I’d like to think of these two functions as our little engines. So cool we can set these aside like that, these CSS Functions will really help for cleaner code.</p> <p>They calculate the exact X and Y coordinates for any point on a circle. To do this, they need to know three things:</p> <ul> <li>The total number of items (<code>--count</code>)</li> <li>Which item we are currently placing (<code>--index</code>),</li> <li>How big the circle should be (<code>--radius</code>)</li> </ul> <p>Let’s break these functions down:</p> <p>First, we need to figure out how big each “slice” of our circular pie is. A full circle is 360 degrees. By dividing 360deg by the total number of siblings (<code>sibling-count()</code>), we get the angle for each segment. If we have 6 items, each one gets a <code>360 / 6 = 60-degree</code> slice of the circle.</p> <p>Next, we determine the specific angle for the current item. We multiply the size of each slice by the item’s position (<code>sibling-index() - 1</code>). Just like in our color spectrum example, we subtract 1 so that our first item starts at an angle of 0 degrees (at the right of the circle).</p> <ul> <li>For our 6 items, the first item would be at <strong>60 * 0 = 0 degrees</strong>.</li> <li>The second item would be at <strong>60 * 1 = 60 degrees</strong>.</li> <li>The third item would be at <strong>60 * 2 = 120 degrees</strong>,</li> <li>and so on…</li> </ul> <p>This is where the trigonometry comes in. The <code>cos()</code> function gives us the X coordinate for a given angle on a circle, while <code>sin()</code> gives us the Y coordinate.</p> <p>Now that our functions are ready, using them is incredibly clean. We apply a <code>transform</code> to each item, passing the dynamic sibling values directly into our functions.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.circle-container div</span> <span class="token punctuation">{</span> <span class="token property">--radius</span><span class="token punctuation">:</span> 120px<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> 50%<span class="token punctuation">;</span> <span class="token property">left</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>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">)</span> <span class="token function">translateX</span><span class="token punctuation">(</span><span class="token function">--pos-x</span><span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">sibling-count</span><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>--radius<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">translateY</span><span class="token punctuation">(</span><span class="token function">--pos-y</span><span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">sibling-count</span><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>--radius<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 start by centering each item perfectly with <code>translate(-50%, -50%)</code>. Then, we apply two more translations. <code>translateX</code> pushes the item horizontally by the amount calculated by our <code>--pos-x</code>-function, and <code>translateY</code> pushes it vertically based on <code>--pos-y</code>.</p> <p>It’s a really cool effect! You can see the items smoothly rearrange themselves to form a new, perfectly symmetrical circle.</p> <p>Now, truth be told… this might not be the best way to create a circle, I’ve seen a very clean way to do this as well by <strong>Temani Afif</strong>. In my example, the radius is rather static, but at the same time gives a bit more layout control. I think both methods have their benefits. But the idea by Temani is really awesome!</p> <p><a href="https://css-tip.com/images-circle/" target="_blank" rel="noreferrer noopener">Check out this method of placing images in a circle with CSS here.</a></p> <h2 id="creating-a-casino-cards-fan-effect-using-sibling-index-and-sibling-count">Creating a casino cards fan effect using sibling-index() and sibling-count()</h2> <p>So far, the demos were mostly about progressing from a start point to an end point. But what if you want to create something symmetrical, fanning out from a central point? Think of holding a hand of playing cards. The cards don’t all lean to one side; they fan out evenly.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Add and remove a card fan effect with sibling-count() and sibling-index()" src="https://codepen.io/utilitybend/embed/preview/qEOQoJe?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/qEOQoJe"> Add and remove a card fan effect with sibling-count() and sibling-index()</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Let’s take a look at the code first:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">--rotation-per-card</span><span class="token punctuation">:</span> 8deg<span class="token punctuation">;</span> <span class="token property">--center-index</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">sibling-count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> + 1<span class="token punctuation">)</span> / 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</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>--rotation-per-card<span class="token punctuation">)</span> * <span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> - <span class="token function">var</span><span class="token punctuation">(</span>--center-index<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token function">translateY</span><span class="token punctuation">(</span><span class="token function">calc</span><span class="token punctuation">(</span>4px * <span class="token punctuation">(</span><span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span> - 1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> bottom center<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>You see that our card has two custom properties, which will be used as levers for this demo. I’ll get to the <code>--rotations-per-card</code> in a bit, but first of all, I want to go to the secret to this entire effect: <code>--center-index</code>.</p> <p>Inside of this <code>--center-index</code> custom property, you can find a simple calculation: <code>calc((sibling-count() + 1) / 2)</code>.</p> <p>This calculation finds the middle item in our list based on the number of siblings, for example:</p> <ul> <li>If we have 5 cards, the calculation is <strong>(5 + 1) / 2</strong>, which gives us <strong>3</strong>. The 3rd card is our center.</li> <li>If we have 6 cards, it’s <strong>(6 + 1) / 2</strong>, which gives us <strong>3.5</strong>. This means the center point is right between the 3rd and 4th card.</li> </ul> <p>Since we already have our center point, we can go on to calculate the rotation for each card. Look at the <code>rotate()</code> part of the transform:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash">calc<span class="token punctuation">(</span>var<span class="token punctuation">(</span>--rotation-per-card<span class="token punctuation">)</span> * <span class="token punctuation">(</span>sibling-index<span class="token punctuation">(</span><span class="token punctuation">)</span> - var<span class="token punctuation">(</span>--center-index<span class="token punctuation">))</span><span class="token punctuation">)</span> </code></pre> <p>This calculates each card’s “distance from the center” and multiplies it by our desired rotation amount (8deg), this is one of those levers we set (<code>--rotation-per-card</code>).</p> <p>Let’s use a 5-card hand for a starting example, where the center index is 3:</p> <ul> <li>Card 1: The distance is 1 - 3 = -2. The rotation is 8deg * -2 = <strong>-16deg</strong> (tilted left).</li> <li>Card 2: The distance is 2 - 3 = -1. The rotation is 8deg * -1 = <strong>-8deg</strong> (tilted slightly left).</li> <li>Card 3: The distance is 3 - 3 = 0. The rotation is 8deg * 0 = <strong>0deg</strong> (perfectly straight).</li> <li>Card 4: The distance is 4 - 3 = 1. The rotation is 8deg * 1 = <strong>8deg</strong> (tilted slightly right).</li> <li>Card 5: The distance is 5 - 3 = 2. The rotation is 8deg * 2 = <strong>16deg</strong> (tilted right).</li> </ul> <p>The result is a symmetrical fan. Cards to the left of the center get a negative rotation, and cards to the right get a positive one.</p> <p>But! There is more! There’s one little effect that was added as the final detail: the <code>translateY()</code>.</p> <p>This pushes each card down slightly, creating the illusion that they are physically stacked on top of one another. The formula <code>calc(4px * (sibling-index() - 1))</code> simply moves each subsequent card down by an additional 4px and adds a bit of depth. That is more of a gut feel than an exact science. Once again, we can rely on <code>sibling-index()</code> to just really finalize this example. Ah, sweet sibling counting, I’m a fan!</p> <h2 id="further-experiments-demos-that-didnt-make-the-article">Further experiments, demos that didn’t make the article</h2> <p>I’m trying to switch up my blogging and demos a bit; Maybe you noticed it on my demos. I wanted to add some information on there for when people come across them in other places. I’m also thinking of writing some more comments. But it’s a fine line between overcommenting and just enough.</p> <p>For this article, I created some other experiments that I thought would be a bit too much. One of them is this bar chart, almost the same as the animation stagger effect, but using a bit of CSS conditionals in there just for funsies! (When the value is 100, it gets a perfect label on top).</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Bar chart using if() and sibling-index" src="https://codepen.io/utilitybend/embed/preview/RNWqMWX?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/RNWqMWX"> Bar chart using if() and sibling-index</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>And last but not least, I have this demo, combining the hue changing with some 3D experimentation. I might make another article about that one specifically. Feel free to give a shout-out if that interests you. I’m not that good at 3D stuff in CSS. I based the demo on a bunch of other pens and did some hacking. But to prettyfy this demo, I had a little <strong>secret</strong>:</p> <p>I put in a prompt to A(I)mit Sheen, who is a true wizard in this sort of thing. If you haven’t heard of his work, you really should! <a href="https://codepen.io/amit_sheen" target="_blank" rel="noreferrer noopener">Check out some of Amit’s work</a>.</p> <p>Here is a little 3D example (And thank you, Amit!):</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="3D adding frames in a rainbow style using sibling-count and sibling-index" src="https://codepen.io/utilitybend/embed/preview/WbQBXpL?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/WbQBXpL"> 3D adding frames in a rainbow style using sibling-count and sibling-index</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="to-close">To close…</h2> <p>I really love this feature, I love it so much that once again, my article might’ve been a bit too long. It’s how my brain works… I start something, and then I get stuck in experimenting and demos. I could cut these articles up into smaller pieces, but it’s just not in my nature. Anyway, for you, who actually went through the whole thing, thank you for your attention! And for those who just skimmed the article for the demos, that’s fine too.</p>Brecht De RuyteHTML popovers shown on interest should be accessible on mobile deviceshttps://utilitybend.com/blog/html-popovers-shown-on-interest-should-be-accessible-on-mobile-devices/https://utilitybend.com/blog/html-popovers-shown-on-interest-should-be-accessible-on-mobile-devices/A little while ago, in Chrome 133, a feature called popover=”hint” landed in the browser. This feature paved the way to something bigger, more specifically, popovers being triggered on hover/focus. This comes with a new attribute “interestfor” and some CSS properties to change the delay speed when showing popovers using that method. There still isn’t much definitive on how this should behave on devices without hover capabilities, and that’s what this article is all about. I want to make sure the content of these popovers is accessible on touch devices.Thu, 07 Aug 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.BI8YMeui_2qacHS.avif 375w, /_astro/visual.BI8YMeui_236AaK.avif 480w, /_astro/visual.BI8YMeui_Z54dnt.avif 680w, /_astro/visual.BI8YMeui_ZuRMkS.avif 800w, /_astro/visual.BI8YMeui_Z1wHivi.avif 980w, /_astro/visual.BI8YMeui_1VX8u.avif 1024w, /_astro/visual.BI8YMeui_ZAWHKx.avif 1660w, /_astro/visual.BI8YMeui_1hD4zt.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.BI8YMeui_ZmOpwu.webp 375w, /_astro/visual.BI8YMeui_ZJS24C.webp 480w, /_astro/visual.BI8YMeui_2c8ib5.webp 680w, /_astro/visual.BI8YMeui_1LjIdF.webp 800w, /_astro/visual.BI8YMeui_Jud3g.webp 980w, /_astro/visual.BI8YMeui_Zpt0Kj.webp 1024w, /_astro/visual.BI8YMeui_Z13nGEl.webp 1660w, /_astro/visual.BI8YMeui_Z2ta4Cy.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.BI8YMeui_Pvm9q.png" srcset="/_astro/visual.BI8YMeui_2mTI54.png 375w, /_astro/visual.BI8YMeui_1YQ6wV.png 480w, /_astro/visual.BI8YMeui_Z8jH1i.png 680w, /_astro/visual.BI8YMeui_Zy8gXH.png 800w, /_astro/visual.BI8YMeui_Z1zWM97.png 980w, /_astro/visual.BI8YMeui_Z1BU5zU.png 1024w, /_astro/visual.BI8YMeui_Z2fOLtW.png 1660w, /_astro/visual.BI8YMeui_RECc7.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1928" height="1149" class="img-fluid"> </picture><p>Most of you reading my blog know I love me some good popovers, and I’m so happy that things are moving forward at a rapid pace. Currently, if you use Chrome Canary with the experimental web platform features enabled, you can play around with the new way to trigger popovers, using a new attribute. I’ll try to keep the intro short in order to make my statement. This post is “general accessibility” on touch devices; I am not the right person to debate about detailed accessibility for specialized assistive technology (e.g., Screen readers).</p> <h2 id="how-do-interestfor-and-popoverhint-work">How do interestfor and popover=“hint” work?</h2> <p>Just as you would do with regular popover using <code>popovertarget</code> (or <code>commandfor</code> if the browser supports it), you can trigger popovers on hover/focus using the following snippet:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">interestfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-popover<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Default speed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>my-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popover</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>hint<span class="token punctuation">&quot;</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">&quot;</span>tooltip<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Hi! I&#39;m a popover<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>These popovers get triggered on hover, and you can update the delays by using CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[interestfor]</span> <span class="token punctuation">{</span> <span class="token property">interest-show-delay</span><span class="token punctuation">:</span> 0.24s<span class="token punctuation">;</span> <span class="token property">interest-hide-delay</span><span class="token punctuation">:</span> 0.44s<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>If you want the same delay for both entry and exit, you can even write a shorthand:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[interestfor]</span> <span class="token punctuation">{</span> <span class="token property">interest-delay</span><span class="token punctuation">:</span> 0.24s<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Both of these actions would be possible on <code>&lt;a&gt;</code> and <code>&lt;button&gt;</code> elements.</p> <p><strong>Accessibility/UX hot take</strong></p> <p>I created a little demo, playing around with the delay parameters. At first, I found the default speed too slow, but to be honest, I think it matters on your use case. If you have a lot of these popovers and a person is tabbing through them using a keyboard, it can get really frustrating and thus not a good UX. In those cases where popovers would just keep jumping up, a bit of extra delay doesn’t hurt. If you only have one or two of them, feel free to ramp up the speed a bit. As in a lot of cases… it depends. Put the user central. So yes.. I kind of like the default speed to be a bit slower as a good default.</p> <p>Here is that delay demo:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Speed checker intersettarget" src="https://codepen.io/utilitybend/embed/preview/PwwQLdb?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/PwwQLdb"> Speed checker intersettarget</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I also added a video if you don’t want to check in Canary:</p> <video playsInline="" width="320" height="240" controls="" preload="metadata" aria-label="Three popovers get triggered on hover, each button that tirggers has a different color (red, yellow, green), they all have different speeds, red is a default speed, the second is a bit faster at .24s and the last one fastest at .14s"><source src="/_astro/interest-timing.jkgnjO16.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>Also note that in this example, I used a <code>role=&quot;tootlip&quot;</code> in the popover. This is still a bit opinionated, and I don’t consider myself an expert on this, so use with care. I came across an <a href="https://github.com/w3c/aria/issues/979" target="_blank" rel="noreferrer noopener">interesting thread on this tooltip role</a> (Also whether tooltips should or should not contain interactive elements).</p> <h3 id="about-popover-hint">About popover hint</h3> <p>There are more nuances than this, but <code>popover=&quot;hint&quot;</code> is used as a new stack of popovers, allowing you to have an auto popover open while toggling between different hints. Think of an example where you would have tooltips inside of an <code>auto</code> popover.</p> <p>I created a little playground that you can use in Chrome Canary to illustrate these differences. (I didn’t bother to create actual “tooltips” from the popover hints with anchoring, to keep the demo on point)</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Popover playground with interest" src="https://codepen.io/utilitybend/embed/preview/ZYbyQyB?default-tab=" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZYbyQyB"> Popover playground with interest</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <video playsInline="" width="320" height="240" controls="" preload="metadata" aria-label="Different stacks of popovers are opened based on auto or hint. hints open on hover, auto on click in this example"><source src="/_astro/popover-playground.DVCeTAQg.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <a href="https://una.im/popover-hint/" target="_blank" rel="noreferrer noopener">There is a bit more information (and a cool example!) to be found on Una’s blog</a> <br> <br> <h2 id="but-what-about-touch-devices">But what about touch devices…</h2> <p>I know for a fact that the Chrome DevRel team has been reaching out to developers at conferences and has done a couple of surveys on the possible solutions to do this. I am quite opinionated on those, but let’s review some of the options first.</p> <p>I’m going to do a quick version on this, but a <a href="https://open-ui.org/components/interest-invokers.explainer/#mockups" target="_blank" rel="noreferrer noopener">full version can be read on the Open UI website</a>; the animated gifs images I’m using are also from that page (but a bit more compressed). I will summarize them and add some of my thoughts on this. I do hope you read the whole thing at Open UI to create your own ideas! We need more input on this.</p> <p>Note that the following part will have some animated GIFs in them; They do not pause, but I’ve hidden them in collapses so you can toggle them if you find them too distracting.</p> <h3 id="option-1a-do-nothing">Option 1a: do nothing</h3> <p>Although this is not in the explainer of Open UI, it is an option that was discussed at CSS Day. Not showing it on mobile devices is one of the options, and I am completely against that idea… But I should at least add the arguments I heard:</p> <h4 id="arguments">Arguments:</h4> <ul> <li>Tooltips shouldn’t contain important information from a UX standpoint, so it’s not needed</li> <li>It makes things easier as we only need to think about one sort of implementation.</li> </ul> <p>The reason I don’t think the argument adds up is that we don’t know which information the authors will put in there. Yes, there shouldn’t be important info in there, but that doesn’t mean there can’t or won’t be. I applaud the argument as it was clearly by a person with good UX intentions, but bad UX is still a thing that will happen - and that is why - I believe that if something is written in a well-structured HTML, it should be accessible by default on every web-capable device. The main benefit of writing some solid and smart HTML fixes most of the accessibility problems we see today. I don’t like the idea of something not visible/accessible by default in just HTML.</p> <p>Take into account translation labels provided in a CMS, where non-technical, non-UX people might have a final say. Before you know it, labels such as “Will increase final price” will be added to that popover.</p> <h3 id="option-1b-let-the-ua-handle-it">Option 1b: Let the UA handle it</h3> <p>Let each browser vendor figure this out by themselves might seem like the easy solution here. However, as someone who started in the Internet Explorer 5 era, I’m always a bit scared of these kinds of solutions.</p> <h4 id="argument">Argument</h4> <ul> <li>Will speed up initial development</li> <li>Behavior can be tailored to the UI/UX direction of the device</li> </ul> <p>Even though I love to see features ship fast, I’d rather have a uniform way of how these interest popovers work. Fewer caveats and author confusion should be important</p> <h3 id="option-2-long-press--show-both-context-menu-and-popover-at-the-same-time">Option 2: Long press = show both context menu and popover at the same time</h3> <p>This idea came initially from open UI. The idea is that the popover and the context menu would both be visible when long pressing an <code>&lt;a&gt;</code> element, and only the popover on a <code>&lt;button&gt;</code> element:</p> <details><summary>View example</summary><div class="content-wrapper" style="display:grid; justify-items: center; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 24px;"><figure style="max-width: 320px;"><img src="https://utilitybend.com/_astro/interesttarget-mockup-show-both.BhOeVJvX.gif" alt="A long press on an anchor opens the popover and the context menu" loading="lazy" width="200" height="133" style="max-width: 320px;"><figcaption>Anchor link</figcaption></figure><figure><img src="https://utilitybend.com/_astro/interesttarget-mockup-button-long-press.jAgI7ifr.gif" alt="A long press on button opens the popover" loading="lazy" width="200" height="133" style="max-width: 320px;"><figcaption>Button</figcaption></figure></div></details> <h4 id="argument-1">Argument:</h4> <ul> <li>There is a lot of native behavior on touch devices where people use a long press</li> </ul> <p>I agree with the idea in general, an author could, for example, add an icon on the link to indicate a tooltip is behind it (for example, using an <code>::after</code> pseudo-element with an info icon). The downside I see is bigger popovers, as we can’t always control what authors put in there.</p> <h3 id="option-3-long-press--include-view-more-info-action-in-context-menu">Option 3: Long press = Include “view more info” action in context menu</h3> <p>Here it is using the long press again, which keeps it the same for buttons, and it would work out of the box. However, for links, the long-press gesture already brings up a context menu. This option proposes adding a “View more info” item to the existing menu.</p> <details><summary>View example</summary><div class="content-wrapper"><figure style="max-width: 320px; margin: 0 auto;"><img src="https://utilitybend.com/_astro/interesttarget-mockup-add-to-context-menu.DLlOUQWW.gif" alt="A context menu opens with a new option to view more info, clicking it opens the popover" loading="lazy" width="200" height="133"></figure></div></details> <h4 id="arguments-1">Arguments:</h4> <ul> <li>It doesn’t alter any existing user experience and is easy to implement.</li> <li>Popovers are not bound to a limited space</li> </ul> <p>I personally like this idea (for me it’s certainly one of the favorites). The biggest downside is that it will take some time for people to get acquainted with this added item in the context menu and that the “view more info” is a fixed label that can’t be altered by the author (I think…).</p> <h3 id="option-35-long-press-and-add-an-opt-in-info-button-pseudo-element">Option 3.5: Long press and add an opt-in “info” button pseudo-element</h3> <p>So the idea here is basically the same, but instead of context menu an optional pseudo-element could be added next to the invoker. That pseudo-element alone would trigger the option.</p> <details><summary>View example</summary><div class="content-wrapper"><figure style="max-width: 320px; margin: 0 auto;"><img src="https://utilitybend.com/_astro/interesttarget-mockup-pseudo-element.DwdIitr3.gif" alt="A peudo-element with an icon next to the link is clicked to open the popover" loading="lazy" width="200" height="133"></figure></div></details> <h4 id="argument-2">Argument:</h4> <ul> <li>clean?</li> </ul> <p>It could look like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">any-pointer</span><span class="token punctuation">:</span> coarse<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.my-link::interest-hint</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&#39;&#39;</span> / <span class="token string">&#39;profile preview&#39;</span><span class="token punctuation">;</span> <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><span class="token string url">&#39;icon.png&#39;</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>I personally think this is a really clean idea, of course people will have to add correct sizing of the icon for the thumb action.</p> <p>But here is that optional part again, and once again, I don’t really like this to be optional by default. I’d rather have the pseudo-element <strong>on by default</strong> with a UA chosen icon if this would be the way to go. At least then, the author needs to explicitly remove or alter it in CSS.</p> <p>But I recently came to a new proposal in Open UI which will be discussed: an <a href="https://github.com/openui/open-ui/issues/1258" target="_blank" rel="noreferrer noopener">Alternative HTML-only version of the ::interest-hint feature, via command invokers</a>.</p> <p>Just to show, we are not there yet which is <strong>exactly why feedback at this stage is extra important</strong>.</p> <h3 id="option-4-single-tap-interest">Option 4: Single-tap interest</h3> <p>Instead of using a long-press, this option suggests that a single-tap on an element with <code>interestfor</code> would show the popover. A second tap (perhaps within a set time) would then activate the link or button.</p> <details><summary>View example</summary><div class="content-wrapper" style="display:grid; justify-items: center; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 24px;"><figure><img src="https://utilitybend.com/_astro/interesttarget-mockup-single-tap.CkpBgFFi.gif" alt="A single tab opens the popover on a link on the device" loading="lazy" width="200" height="133" style="max-width: 320px;"><figcaption>Anchor link</figcaption></figure><figure><img src="https://utilitybend.com/_astro/interesttarget-mockup-button-single-tap.BQqZVYGM.gif" alt="A single tab opens the popover on a button on the device" loading="lazy" width="200" height="133" style="max-width: 320px;"><figcaption>Button</figcaption></figure></div></details> <p><strong>Argument</strong>: Since a single-tap is used for the popover, the user can still long-press to access the standard context menu.</p> <p>But the downside here is something called “<strong>tap uncertainty</strong>”. Users won’t know if a single tap will navigate to a new page or just show a popover, which could be confusing and discourage link-tapping, maybe even create dark-patterns.</p> <h3 id="option-5-do-not-show-the-context-menu">Option 5: Do not show the context menu</h3> <p>With this option, long-pressing an element with <code>interestfor</code> would immediately trigger the popover, but the standard context menu would not appear.</p> <details><summary>View example</summary><div class="content-wrapper"><figure style="max-width: 320px; margin: 0 auto;"><img src="https://utilitybend.com/_astro/interesttarget-mockup-no-context-menu.DLx32oV0.gif" alt="A popover opens on long press without a context menu of the device" loading="lazy" width="200" height="133"></figure></div></details> <h4 id="argument-3">Argument:</h4> <ul> <li>Direct and clear action</li> </ul> <p>The downside is simple: but what if I want to copy text, open in new tab,… There is loss of functionality here.</p> <h3 id="option-6-a-new-gesture-and-alternatives">Option 6: A new gesture and alternatives</h3> <p>I don’t have anything here for gestures 🙂 Think about it.</p> <h2 id="did-i-pop-your-interest">Did I pop your interest?</h2> <p>Which option to choose, that’s the question, right? I did not hide that I have my favourites, but that doesn’t mean they are the best ones. I don’t have the direct answer here on which on of the options should be provided. I know for sure it will be a hassle to get every user agent on board for a single solution.</p> <p>But if you read this, I do want you to share your opinion on this. For me, it needs to tick the following boxes:</p> <ul> <li>Make it accessible on a mobile/touch device by default, as we don’t know the content (it’s fine if an author can turn it off)</li> <li>Make it a uniform way between user agents, so that there isn’t any author confusion</li> <li>Make it clear to users that something else will happen (can be partially by author, such as adding an icon)</li> </ul>Brecht De RuyteThe customizable select - Part five: Optgroup, creating a true select menuhttps://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu/https://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu/In this part of my customizable select series, I want to highlight option groups! Option groups are part of our select interactions to provide clarity, and now, they can be styled. This will certainly not be the last demo I did on the select, and more updates are coming to this element. With a wink to the past, we will be creating a restaurant dish picker, a true “Select Menu”.Fri, 11 Jul 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.DFADr2bm_ZAyosb.avif 375w, /_astro/visual.DFADr2bm_ZXC10j.avif 480w, /_astro/visual.DFADr2bm_1Xojfo.avif 680w, /_astro/visual.DFADr2bm_1xzJhY.avif 800w, /_astro/visual.DFADr2bm_vKe7z.avif 980w, /_astro/visual.DFADr2bm_Z2j8jJI.avif 1024w, /_astro/visual.DFADr2bm_2898ab.avif 1660w, /_astro/visual.DFADr2bm_Z13rdiJ.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.DFADr2bm_1FD76n.webp 375w, /_astro/visual.DFADr2bm_1izuyf.webp 480w, /_astro/visual.DFADr2bm_ZOAiYY.webp 680w, /_astro/visual.DFADr2bm_Z1foRWo.webp 800w, /_astro/visual.DFADr2bm_Z2heo7N.webp 980w, /_astro/visual.DFADr2bm_2jCPap.webp 1024w, /_astro/visual.DFADr2bm_1FI9gn.webp 1660w, /_astro/visual.DFADr2bm_fVLia.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.DFADr2bm_Z1uyUIM.png" srcset="/_astro/visual.DFADr2bm_ZDNS60.png 375w, /_astro/visual.DFADr2bm_Z11RuD8.png 480w, /_astro/visual.DFADr2bm_1U8OBz.png 680w, /_astro/visual.DFADr2bm_1ukfEa.png 800w, /_astro/visual.DFADr2bm_suJtK.png 980w, /_astro/visual.DFADr2bm_17bKkN.png 1024w, /_astro/visual.DFADr2bm_th4qL.png 1660w, /_astro/visual.DFADr2bm_Z1spEG6.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1928" height="1149" class="img-fluid"> </picture> <h2 id="quick-recap">Quick recap</h2> <p>If you were following this series, I’m sure you are pretty much in love with the select element and have been tinkering with it at least for a bit. I am not going to go on about how the progressive enhancement works in this article and the various elements. I’ll just quickly talk about the idea and fundamentals of this demo and some others.</p> <p>If you do get lost, I suggest you go back to the previous in this series:</p> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">Part one: history, trickery, and styling the select with CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">Part two: Potions, anchoring, and radial shenanigans in CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options">Part three: Sticky Options</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification">Part four: Scroll snapping, state queries, monster hunter, and gamification</a></li> </ul> <h2 id="what-were-making">What we’re making</h2> <p>Here is the full demo showing a menu-picker:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="An actual Select Menu with optgroups" src="https://codepen.io/utilitybend/embed/preview/ByoBMBm?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ByoBMBm"> An actual Select Menu with optgroups</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="adding-our-html-a-select-with-optgroup">Adding our HTML: A select with optgroup</h2> <p>In the final demo, there are a few of these selects, but we’ll be focusing on just one, as the others are just a copy. First of all, let’s set our HTML, a <code>&lt;select&gt;</code> with categories in <code>&lt;optgroup&gt;</code> elements and some <code>&lt;option&gt;</code>s inside. Let me jump right in with a full example and add an explainer below:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>main-course-select<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Main Course <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>main_course<span class="token punctuation">&quot;</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">&quot;</span>main-course-select<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- This wrapper is for some extra styling --&gt;</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">&quot;</span>menu-options<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- placeholder text --&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token attr-name">selected</span> <span class="token attr-name">disabled</span><span class="token punctuation">&gt;</span></span>Select a main course...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>optgroup</span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>Meat &amp; Poultry<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>filet_mignon<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Filet Mignon <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">&quot;</span>info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Served with potato gratin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>rack_of_lamb<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Rack of Lamb <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">&quot;</span>info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>With mint &amp; root vegetables<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>optgroup</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>optgroup</span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>Fish<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>sea_bass<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Pan-Seared Sea Bass <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">&quot;</span>info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>With lemon-caper sauce<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>optgroup</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>optgroup</span> <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>Vegetarian<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>risotto_funghi<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Mushroom Risotto <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">&quot;</span>info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>With truffle &amp; parmesan<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>gnocchi<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Handmade Gnocchi <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">&quot;</span>info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>With sage butter sauce<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>optgroup</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>A few things are noteworthy here:</p> <ul> <li>We added a <code>&lt;label&gt;</code> for our <code>&lt;select&gt;</code>, which is just good practice, especially for assistive technology users.</li> <li>We will be using the <code>&lt;selectedcontent&gt;</code> element to style our selected option’s content.</li> <li>We created a placeholder text for our select with an option that is both <code>disabled</code> and <code>selected</code></li> <li>We categorized our dishes into <code>&lt;optgroup&gt;</code>, giving each a distinct <code>label</code></li> <li>Inside our options, we have an extra <code>&lt;span&gt;</code> that will be used for some smaller text</li> <li>We have an extra <code>.wrapper</code> div-element, which we’ll need for styling</li> </ul> <p><strong>Note that the date in this select is different from in the final demo, but I wanted to keep the text of the options a bit shorter in the article.</strong></p> <h2 id="the-base-styles-for-all-browsers">The base styles for all browsers</h2> <p>The first thing we want to do is create our base styles. To keep things simple, we’ll just add the opt-in somewhere separately:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <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 atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>And first, let’s create our select, there are a bunch of variables I’m using as constants, they are mainly for colours, and borders, but I’ll add these here for completeness:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--text-dark</span><span class="token punctuation">:</span> #5a4a42<span class="token punctuation">;</span> <span class="token property">--text-heading</span><span class="token punctuation">:</span> #8c6b4f<span class="token punctuation">;</span> <span class="token property">--border</span><span class="token punctuation">:</span> #dcd3c9<span class="token punctuation">;</span> <span class="token property">--border-accent</span><span class="token punctuation">:</span> #e0cda9<span class="token punctuation">;</span> <span class="token property">--border-hover</span><span class="token punctuation">:</span> #8c6b4f<span class="token punctuation">;</span> <span class="token property">--font-heading</span><span class="token punctuation">:</span> <span class="token string">&quot;Fleur De Leah&quot;</span><span class="token punctuation">,</span> cursive<span class="token punctuation">;</span> <span class="token property">--font-body</span><span class="token punctuation">:</span> <span class="token string">&quot;Lato&quot;</span><span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token property">--card-max-width</span><span class="token punctuation">:</span> 700px<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>And here is the styling of that <code>&lt;select&gt;</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><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">padding</span><span class="token punctuation">:</span> 1rem 2.5rem 1rem 1rem<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>--border<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--border-radius<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 url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">&quot;chevron-down.svg&quot;</span><span class="token punctuation">)</span></span><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 property">background-position</span><span class="token punctuation">:</span> right 1rem center<span class="token punctuation">;</span> <span class="token property">background-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-body<span class="token punctuation">)</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-weight</span><span class="token punctuation">:</span> 700<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>--text-dark<span class="token punctuation">)</span><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">transition</span><span class="token punctuation">:</span> border-color 0.3s ease<span class="token punctuation">;</span> <span class="token selector">&amp;:where(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">border-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--border-hover<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This bit of default styling gives us a good starting point and will be available in all browsers:</p> <p>Withs some extra styling on the label this is that result:</p> <picture> <source srcset="/_astro/select-closed.DfeXCdlv_2at612.avif 320w, /_astro/select-closed.DfeXCdlv_Lv0aM.avif 480w, /_astro/select-closed.DfeXCdlv_Z1TdKNL.avif 800w, /_astro/select-closed.DfeXCdlv_Z2e9DWh.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-closed.DfeXCdlv_Z2wVCqd.webp 320w, /_astro/select-closed.DfeXCdlv_19hpxt.webp 480w, /_astro/select-closed.DfeXCdlv_Z1wrlr5.webp 800w, /_astro/select-closed.DfeXCdlv_Z1QnezA.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-closed.DfeXCdlv_d0MGS.png" srcset="/_astro/select-closed.DfeXCdlv_2bQIVd.png 320w, /_astro/select-closed.DfeXCdlv_MSD5X.png 480w, /_astro/select-closed.DfeXCdlv_Z1RP7SA.png 800w, /_astro/select-closed.DfeXCdlv_Z2cL126.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select in a brownish tint allowing to select a course of a menu" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But yes, when opened we get our default picker as a fallback, still pretty usable and slick to start with.</p> <picture> <source srcset="/_astro/select-open.2nG4d9dj_ZI6LbB.avif 320w, /_astro/select-open.2nG4d9dj_Z2kUlJB.avif 480w, /_astro/select-open.2nG4d9dj_HS17R.avif 800w, /_astro/select-open.2nG4d9dj_Z1rQ24O.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-open.2nG4d9dj_Z1woTEz.webp 320w, /_astro/select-open.2nG4d9dj_1UXDAm.webp 480w, /_astro/select-open.2nG4d9dj_Z5p7l6.webp 800w, /_astro/select-open.2nG4d9dj_Z2g9axM.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-open.2nG4d9dj_U6LPe.png" srcset="/_astro/select-open.2nG4d9dj_1J1ONM.png 320w, /_astro/select-open.2nG4d9dj_7dffM.png 480w, /_astro/select-open.2nG4d9dj_Z1TavFF.png 800w, /_astro/select-open.2nG4d9dj_10hyUz.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="An open select with some course choices for a menu" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Time go custom!</p> <h2 id="creating-our-menu-choice">Creating our menu choice</h2> <p>The next part is best to be wrapped inside the <code>@supports</code> query. I’ll break it down step by step:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">body:has(select:open)</span> <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> </code></pre> <p>This is quite the heavy selector, but we’ll be needing it as we want our <code>::picker(select)</code> to take the full height of the screen.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1rem 2.5rem<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 selector">&amp;::picker-icon</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">::checkmark</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> <p>We are updating the paddings, removing the <code>::picker-icon</code>, and hide the <code>:checkmark</code> inside of our <code>&lt;select&gt;</code>.</p> <p>These are all things we’ve done before, but we’re going somewhere new in the next bit.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">gap</span><span class="token punctuation">:</span> 3vw<span class="token punctuation">;</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --body<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 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">max-height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> opacity<span class="token punctuation">,</span> translate<span class="token punctuation">,</span> display<span class="token punctuation">,</span> overlay<span class="token punctuation">;</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.4s<span class="token punctuation">;</span> <span class="token property">transition-timing-function</span><span class="token punctuation">:</span> ease-in-out<span class="token punctuation">;</span> <span class="token property">transition-behavior</span><span class="token punctuation">:</span> allow-discrete<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>There are a few things going on here with the <code>::picker(select)</code>. We’re going to give it a “fake” <code>position-anchor</code> (or you could actually give your <code>&lt;body&gt;</code> that <code>anchor-name</code> if you want to…). This allows us to set our picker <code>fixed</code> to the root element instead of the select itself, allowing us to create something full width and height.</p> <p>We’ll also set up a smooth animation. The picker starts out invisible (<code>opacity: 0</code>) and then we’ll transition its <code>opacity</code> to fade it in and its <code>translate</code> property to move it into place with <code>@starting-style</code> later on. To make sure the top-layer transition works perfectly, we also animate two discrete properties: <code>display</code> and <code>overlay</code>.</p> <p>The open state for that transition will be the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select:open::picker(select)</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">translate</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 20vh<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Next up, I wanted my options to flow in two columns. I did this with CSS columns and a simple media query, but feel free to give it your own spin. This is why we needed the extra wrapper; Otherwise, we would run into overflow issues:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.menu-options</span> <span class="token punctuation">{</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>width &gt; 600px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">columns</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span> <span class="token property">columns-width</span><span class="token punctuation">:</span> 45vw<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h2 id="styling-the-optgroup-and-options">Styling the optgroup and options</h2> <p>All that is left to do is some basic styling.</p> <p>Just as our options we can now style our optgroup:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">optgroup</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.5rem 1.5rem 0.5rem<span class="token punctuation">;</span> <span class="token property">margin-top</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span> <span class="token property">border-bottom</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--border-accent<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 function">var</span><span class="token punctuation">(</span>--font-heading<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2.25rem<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">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> <span class="token function">var</span><span class="token punctuation">(</span>--text-heading<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> default<span class="token punctuation">;</span> <span class="token selector">&amp;:first-of-type</span> <span class="token punctuation">{</span> <span class="token property">margin-top</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>That’s it! I did some extra things for the options, which might be interesting. I will remove the presentation styles in the next example. Feel free to see the demo for those:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</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-direction</span><span class="token punctuation">:</span> column<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 property">break-inside</span><span class="token punctuation">:</span> avoid<span class="token punctuation">;</span> <span class="token selector">&amp;:first-of-type</span> <span class="token punctuation">{</span> <span class="token property">break-before</span><span class="token punctuation">:</span> avoid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:disabled</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> <p>These are some of the extra-special things I did with the option. First, we set the <code>white-space</code> to <code>normal</code> again, countering the UA-stylesheet. We do not want a break with the CSS column inside the options, so this is why we have <code>break-inside: avoid;</code> in there.</p> <p>For cleaner presentation, we also don’t want an <code>&lt;optgroup&gt;</code> text separated from he first option, which is why the first option in an option group is set to <code>break-before: avoid</code>.</p> <p>We also hide our <code>disabled</code> placeholder select from the select itself.</p> <p>To finalize, we style our extra <code>.info</code> a bit and center our <code>&lt;selectedcontent&gt;</code> to match the styles:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.info</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">margin-top</span><span class="token punctuation">:</span> 0.25rem<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">font-weight</span><span class="token punctuation">:</span> 400<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">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> <span class="token function">var</span><span class="token punctuation">(</span>--text-heading<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">selectedcontent</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 punctuation">}</span> </code></pre> <p>Here is that final demo again.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="An actual Select Menu with optgroups" src="https://codepen.io/utilitybend/embed/preview/ByoBMBm?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ByoBMBm"> An actual Select Menu with optgroups</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="more-fun-with-option-groups">More fun with option groups</h2> <p>Another idea I did with <code>&lt;optgroup&gt;</code> is the following:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Customizable select optgroup" src="https://codepen.io/utilitybend/embed/preview/jEOpwXX?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/jEOpwXX"> Customizable select optgroup</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Based on my previous potion selector demo, I went with a radial theme. In this example, the <code>&lt;optgroup&gt;</code> text is only visible when hovering. This is the trick behind this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">optgroup</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::after</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>value<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>--text-color<span class="token punctuation">)</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">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:hover, &amp;:has(option:hover), &amp;:has(option:focus-visible)</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::after</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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>What happens here is that we don’t visually show the optgroup but instead use an <code>::after</code> pseudo-element that uses the <code>attr()</code> syntax to contain the value op the <code>&lt;optgroup&gt;</code> inside the <code>content</code> property. Next up, we show this pseudo-element when the <code>&lt;optgroup&gt;</code> is hovered, or an option inside that optgroup is hovered/focused.</p> <h2 id="conclusion">Conclusion</h2> <p>I can’t wait to see what people will do with this new power where we can also style the <code>&lt;optgroup&gt;</code>. I always had some sort of country selector in mind where the optgroup, for example, could be the layout of Europe, and the countries could be options. Feel free to steal that idea, I’d love to see it!</p> <p>This almost concludes my series on the customizable select for now. I will be doing one more in a week or so, just to quickly highlight some other demos I created without going in-depth, because they do deserve a place on this blog.</p> <h3 id="other-articles-on-select">Other articles on Select:</h3> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification">The customizable select - Part four: Scroll snapping, state queries, monster hunter, and gamification</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options">The customizable select - Part three: Sticky Options</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">The customizable select - Part two: Potions, anchoring, and radial shenanigans in CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">The customizable select - Part one: history, trickery, and styling the select with CSS</a></li> </ul>Brecht De RuyteThe customizable select - Part four: Scroll snapping, state queries, monster hunter, and gamificationhttps://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification/https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification/In this (long) part of the customizable select series, it’s all about gamification. In this article, I’d like to highlight one of my demos, where I aimed to recreate a piece of UI found in the Monster Hunter games. To re-create this behavior, I had to think in terms of keyboard navigation first. This demo requires quite a lot of CSS, as well as some scripting, and in the end, I do want to highlight some accessibility concerns. This is an experiment on how far we can take it when styling select elements.Fri, 20 Jun 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.BbaQeyBV_2grnl0.avif 375w, /_astro/visual.BbaQeyBV_XOq2I.avif 480w, /_astro/visual.BbaQeyBV_Zrc9h1.avif 680w, /_astro/visual.BbaQeyBV_1iIB6L.avif 800w, /_astro/visual.BbaQeyBV_tAIB2.avif 980w, /_astro/visual.BbaQeyBV_1GflKo.avif 1024w, /_astro/visual.BbaQeyBV_1ONHKo.avif 1660w, /_astro/visual.BbaQeyBV_HxzdQ.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.BbaQeyBV_ZwxeTn.webp 375w, /_astro/visual.BbaQeyBV_Z1OaccE.webp 480w, /_astro/visual.BbaQeyBV_1P0mhx.webp 680w, /_astro/visual.BbaQeyBV_Z1ug18B.webp 800w, /_astro/visual.BbaQeyBV_Z2jnSDl.webp 980w, /_astro/visual.BbaQeyBV_1eOmQA.webp 1024w, /_astro/visual.BbaQeyBV_1nnIQA.webp 1660w, /_astro/visual.BbaQeyBV_21VyOK.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.BbaQeyBV_13qLaQ.png" srcset="/_astro/visual.BbaQeyBV_2dbSHb.png 375w, /_astro/visual.BbaQeyBV_UyVoT.png 480w, /_astro/visual.BbaQeyBV_ZurCTP.png 680w, /_astro/visual.BbaQeyBV_1ft7sW.png 800w, /_astro/visual.BbaQeyBV_qleXd.png 980w, /_astro/visual.BbaQeyBV_2ni1Y.png 1024w, /_astro/visual.BbaQeyBV_aVE1Y.png 1660w, /_astro/visual.BbaQeyBV_iz7Pu.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2716" height="1528" class="img-fluid"> </picture> <p>This is a long one… and I still wasn’t able to cover everything. So I can understand if you want to jump to some sections.</p> <ul> <li><a href="#setting-up-our-html">Setting up the HTML</a></li> <li><a href="#basic-css-setup-and-not-so-progressive-enhancement">CSS setup</a></li> <li><a href="#adding-our-gamification-behavior-with-javascript">Adding the gamification with JavaScript</a></li> <li><a href="#accessibility-context-is-everything-here">Accessibility: context is everything here</a></li> <li><a href="#conclusion">Conclusion</a></li> </ul> <p>For those that just want to play around with it, here is that final result already 😉</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Monster Hunter World item wheel with customizable select" src="https://codepen.io/utilitybend/embed/poMpNbg?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/poMpNbg"> Monster Hunter World item wheel with customizable select</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="the-idea-monster-hunter-games">The idea: Monster Hunter games</h2> <p>I am a big fan of the Monster Hunter games, with “Monster Hunter Wilds” being the last in the series. I started this demo while waiting for that last entry to be released, so visually it has more of a Monster Hunter Wold vibe (the previous version). If you are clueless about what I’m going on about, here is a video that shows the UI in the game:</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/ADynVA5wXOM?si=iCR-etCXBsExmhHZ&start=536" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe></div> <p>I also added a little screenshot for easy reference:</p> <picture> <source srcset="/_astro/mh-screenshot.C4pK1PnV_Z1OX72B.avif 320w, /_astro/mh-screenshot.C4pK1PnV_Z1gXfsp.avif 480w, /_astro/mh-screenshot.C4pK1PnV_WBF3E.avif 800w, /_astro/mh-screenshot.C4pK1PnV_21tj2s.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/mh-screenshot.C4pK1PnV_Zqgv98.webp 320w, /_astro/mh-screenshot.C4pK1PnV_7Ilq4.webp 480w, /_astro/mh-screenshot.C4pK1PnV_2mjgW8.webp 800w, /_astro/mh-screenshot.C4pK1PnV_Z1E1dS0.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/mh-screenshot.C4pK1PnV_1vRiNf.png" srcset="/_astro/mh-screenshot.C4pK1PnV_Z17Wku1.png 320w, /_astro/mh-screenshot.C4pK1PnV_ZyWsTO.png 480w, /_astro/mh-screenshot.C4pK1PnV_1ECrBf.png 800w, /_astro/mh-screenshot.C4pK1PnV_Z2lH3dS.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select in a game where the item in che center is the item selected, there are icons of a d-pad on each sides howing which button to press to scroll left and right, 5 items are visible, the center item is the selected one" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h2 id="where-we-left-off">Where we left off…</h2> <p>In the previous parts, we’ve covered basic styling, radial positioning, and sticky options. This time, we’re going horizontal and focusing on keystrokes and making it draggable. It’s a different idea, but I think the result turned out really cool. It’s quite a long demo so I won’t be going over every border or style in detail. Instead, I’d like to highlight a few parts on how this actually works.</p> <h2 id="setting-up-our-html">Setting up our HTML</h2> <p>Let’s start with the structure. It’s a bit more complex than our previous demos because we need scroll arrows and an extra frame, more about those later on.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</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">&quot;</span>Monster Hunter items<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>trigger<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>frame<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>items<span class="token punctuation">&quot;</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">&quot;</span>itemlist<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>arrow arrow-left<span class="token punctuation">&quot;</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">&quot;</span>button<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- Left arrow SVG --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>item<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>icon<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>#potion<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</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">&quot;</span>title<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Potion<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>amount<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>10<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- More options... --&gt;</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">&quot;</span>arrow arrow-right<span class="token punctuation">&quot;</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">&quot;</span>button<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- Right arrow SVG --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <h3 id="the-options">The options</h3> <p>The options consist out of a few elements that we’re going to position differently. Each of the options will hold an icon with the image of an item, a <code>.title</code> containing a name and an <code>.amount</code> containing how many items are left of the type.</p> <h3 id="the-extra-elements">The extra elements</h3> <p>There are few extra divs to found here, more info on them below but for a fast read, here is a run down of why certain elements are added:</p> <ul> <li>The <code>.frame</code> is used because we want the items to be visually dragged behind the frame, the item in the frame is the focussed item which will be selected</li> <li>The extra <code>.items</code> element is what we’ll need to set up our scroll-snapping</li> <li>The arrows are for single pointer modality</li> </ul> <p>We are taking things a bit further here with the <code>.frame</code> element and the scroll arrows. Notice that I’m adding the <code>type=&quot;button&quot;</code> on the scroll arrows as well as I don’t want the opt-in to think of this as a trigger button. The arrows are added because I wanted to make this accessible as possible by at least offering a single pointer modality. More on the accessibility part of things later on because it does raise a few questions.</p> <h2 id="basic-css-setup-and-not-so-progressive-enhancement">Basic CSS setup and not so progressive enhancement…</h2> <p>For this demo I didn’t really go the full progressive enhancement route. Don’t get me wrong, it’s still a fully functional select, it’s just not styled because I thought the CSS was already getting complicated enough. I still added a feature query tho.</p> <p>First of all, let’s set some variables we’ll be using throughout this demo. They are pretty much self-explanatory, just your colors, borders, and sizes.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--base-icon-size</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">--icon-size-wrap</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span> <span class="token property">--btn-bg</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>70 70 70 / 0.9<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--border-color</span><span class="token punctuation">:</span> #282929<span class="token punctuation">;</span> <span class="token property">--border-width</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>Sizes to note:</strong></p> <ul> <li>The <code>--base-icon-size</code> will be the size of an option</li> <li>The <code>--icon-size-wrap</code> will be the actual size of an icon.</li> </ul> <p>Next up, it’s time to set our opt-in. The following part of the CSS setup will be wrapped in a feature query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">select, ::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Next parts in here */</span> <span class="token punctuation">}</span> </code></pre> <p>Ok, let’s start with some of that basic select styling:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</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">display</span><span class="token punctuation">:</span> flex<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">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --my-select<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> none<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 selector">&amp;::picker-icon</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> <p>A few things are going on here. The first thing to notice is that the select is relatively positioned. The reason for this is that we’ll be placing some pseudo-elements on it later on. However, compared to my other demos, where I position the picker on top of the select, for this one, I am setting my custom <code>anchor-name</code> to the select element. We will be hanging a lot more items on the select, such as the <code>.title</code> and <code>.amount</code>, which is why we need that bit of extra control.</p> <p>We’re also hiding the <code>::picker-icon</code> again.</p> <h2 id="the-borders-and-selectedcontent-on-open-state">The borders and selectedcontent on open state</h2> <p>In the example UI, we see a bunch of borders. To create this kind of behavior, I wanted to add those to the open state of the select. Feel free to visit the example in detail to get the full styling of it. Another important thing that we wanted to do is that we don’t see the SVG in our <code>&lt;selectedcontent&gt;</code> when the select is <code>:open</code>, so we’ll make that one invisible as well.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* previous styles */</span> <span class="token selector">&amp;:open</span> <span class="token punctuation">{</span> <span class="token selector">selectedcontent svg</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* The horizontal lines in our options */</span> <span class="token selector">&amp;::before, &amp;::after</span> <span class="token punctuation">{</span> <span class="token comment">/* Border styles and positioning */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h2 id="creating-the-frame">Creating the frame</h2> <p>This was the trickiest part to figure out. I wanted items to scroll under a frame, like they’re behind glass. The solution was to create two identical-looking elements, one of those will be our button holding the <code>&lt;selected&gt;</code> content, which I gave the <code>.trigger</code> class for convenience, the other one is the dedicated <code>.frame</code> element:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger, .frame</span> <span class="token punctuation">{</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--base-icon-size<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</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--border-width<span class="token punctuation">)</span> 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 property">border-radius</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token selector">&amp;::before, &amp;::after</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-block-start</span><span class="token punctuation">:</span> 40%<span class="token punctuation">;</span> <span class="token property">inset-inline</span><span class="token punctuation">:</span> -17px auto<span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 30px<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">background</span><span class="token punctuation">:</span> <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 property">clip-path</span><span class="token punctuation">:</span> <span class="token function">polygon</span><span class="token punctuation">(</span>50% 0%<span class="token punctuation">,</span> 80% 50%<span class="token punctuation">,</span> 50% 100%<span class="token punctuation">,</span> 20% 50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::after</span> <span class="token punctuation">{</span> <span class="token property">inset-inline</span><span class="token punctuation">:</span> auto -17px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>But, there is more, we’re going full anchoring in this demo by setting an <code>anchor-name</code> for the trigger button (<code>--button</code>) and for the frame (<code>--frame</code>). While also providing a bit of positioning. We also want a visible overflow on the trigger because we’re going to position some of the text in our <code>&lt;selectedcontent&gt;</code> outside of our <code>&lt;button&gt;</code>.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.trigger</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --button<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> visible<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.frame</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> 0<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</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 property">anchor-name</span><span class="token punctuation">:</span> --frame<span class="token punctuation">;</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 <code>.trigger</code> button is what we’ll see when the select is closed. The <code>.frame</code> sits on top with <code>pointer-events: none</code> so that we can drag through it. Those little diamond shapes hanging on the frame are made with pseudo-elements using a <code>clip-path</code> to give a bit more that MH World feeling.</p> <h2 id="styling-the-picker-and-options">Styling the picker and options</h2> <p>First thing we need to do is set the <code>::picker(select)</code> to be anchored to the <code>--button</code> in this case and remove some of those UA-styles</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --button<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">top</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-icon-size<span class="token punctuation">)</span> / -1<span class="token punctuation">)</span><span class="token punctuation">;</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 property">padding</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Maybe not a bad idea to take a look at what we have now:</p> <picture> <source srcset="/_astro/borders-frames.BUMssmcK_Z1HcMWy.avif 320w, /_astro/borders-frames.BUMssmcK_Z1HCYgQ.avif 480w, /_astro/borders-frames.BUMssmcK_zYcKu.avif 800w, /_astro/borders-frames.BUMssmcK_Z11h7Ts.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/borders-frames.BUMssmcK_19PMaH.webp 320w, /_astro/borders-frames.BUMssmcK_19pAQp.webp 480w, /_astro/borders-frames.BUMssmcK_Z1C9kUb.webp 800w, /_astro/borders-frames.BUMssmcK_1PLsdN.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/borders-frames.BUMssmcK_Z1lpH2x.png" srcset="/_astro/borders-frames.BUMssmcK_ZbBda0.png 320w, /_astro/borders-frames.BUMssmcK_Zc2oti.png 480w, /_astro/borders-frames.BUMssmcK_26zMy3.png 800w, /_astro/borders-frames.BUMssmcK_ujrS6.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select in a game where the item in che center is the item selected, there are icons of a d-pad on each sides howing which button to press to scroll left and right, 5 items are visible, the center item is the selected one" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Ok, it still looks a bit messy, but we can see a few things happening: we see our double frames, our borders, and our texts. We almost got the complete setup. We have a bit of an offset for the frame, that is because we are using the icon size to calculate the top, this should be fixed when we position the text inside of our options.</p> <h3 id="setting-up-the-horizontal-scrolling">Setting up the horizontal scrolling</h3> <p>We are once again using the <code>--base-icon-size</code> variable to calculate the width of our flex container. We are setting a <code>scroll-snap</code> on the x-axis and will remove the scrollbar for this example:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.items</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">max-width</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-icon-size<span class="token punctuation">)</span> * 5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 0 <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--base-icon-size<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<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 property">scrollbar-width</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>That padding is also super important, it lets the first and last items scroll to the center. Without it, they’d get stuck at the edges.</p> <h3 id="styling-our-options">Styling our options</h3> <p>Without completely showing every style of the options. In essence, we decide to set the option to have a relative position and hide the checkmark.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</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 comment">/* Look and feel styles */</span> <span class="token selector">&amp;::checkmark</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> <h3 id="scroll-state-queries-for-snapped-position">Scroll-state queries for snapped position</h3> <p>The SVGs inside start small and scale up when they are snapped, just as in the example. This is a great idea for <code>scroll-state</code> queries. I have written a small <a href="https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css">guide to scroll-state queries</a> in the past, but for this demo, this is the gist of it:</p> <p>First, we set the SVG in the option to have a smaller size overall:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option svg</span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 0.6<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> scale 0.2s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>If your browser supports <code>scroll-state</code> container queries, we can detect when an item snaps:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</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">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">svg</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">container-type</span><span class="token punctuation">:</span> scroll-state<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">scroll-state</span><span class="token punctuation">(</span><span class="token property">snapped</span><span class="token punctuation">:</span> inline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 1<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>This is pretty experimental stuff, but when it works, it’s really smooth. The item scales up automatically when it snaps to center.</p> <p>That’s it for our picker and options styling. This is what we have so far</p> <picture> <source srcset="/_astro/horizontal.jzMBzpSK_1tCYVE.avif 320w, /_astro/horizontal.jzMBzpSK_29sEdj.avif 480w, /_astro/horizontal.jzMBzpSK_1MrbQJ.avif 800w, /_astro/horizontal.jzMBzpSK_Z2f08yR.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/horizontal.jzMBzpSK_1M3Lss.webp 320w, /_astro/horizontal.jzMBzpSK_2rSqJ7.webp 480w, /_astro/horizontal.jzMBzpSK_25QXnx.webp 800w, /_astro/horizontal.jzMBzpSK_Z1Vzm34.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/horizontal.jzMBzpSK_ZcbnqM.png" srcset="/_astro/horizontal.jzMBzpSK_ZoAg2Y.png 320w, /_astro/horizontal.jzMBzpSK_geodF.png 480w, /_astro/horizontal.jzMBzpSK_Z5M47T.png 800w, /_astro/horizontal.jzMBzpSK_VWJfq.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select in a game where the item in che center is the item selected, there are icons of a d-pad on each sides howing which button to press to scroll left and right, 5 items are visible, the center item is the selected one" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>We got some scrolling here, it’s great! But we still have to handle the text in those options.</p> <h2 id="adding-the-title-and-amount-displays">Adding the title and amount displays</h2> <p>Using anchor positioning again, we can place the title and amount relative to our select:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.title</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --my-select<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><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>-50%<span class="token punctuation">,</span> 15px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* presentation styles */</span> <span class="token punctuation">}</span> </code></pre> <p>The amount gets positioned in the corner:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.amount</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --my-select<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Presentation styles */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="adding-the-arrows">Adding the arrows</h2> <p>It’s a great idea to add some arrows in here to scroll the pane (more on the JS side of things later on). The styling of the arrows is very straightforward and just uses absolute positioning based on its select container.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.arrow</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 comment">/* Presentation styles, positioning and hover/focus states */</span> <span class="token punctuation">}</span> </code></pre> <p>And with some added presentation styles in general, we already have our design in order:</p> <picture> <source srcset="/_astro/visual.BbaQeyBV_rY9r.avif 320w, /_astro/visual.BbaQeyBV_1wc3EV.avif 480w, /_astro/visual.BbaQeyBV_ZJjS5z.avif 800w, /_astro/visual.BbaQeyBV_Z1Eady8.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/visual.BbaQeyBV_2hEuI0.webp 320w, /_astro/visual.BbaQeyBV_Z1gMyzr.webp 480w, /_astro/visual.BbaQeyBV_1wRCsY.webp 800w, /_astro/visual.BbaQeyBV_C2i0q.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/visual.BbaQeyBV_Z1chGOM.png" srcset="/_astro/visual.BbaQeyBV_Z2Mutn.png 320w, /_astro/visual.BbaQeyBV_1sVz27.png 480w, /_astro/visual.BbaQeyBV_ZMzmIo.png 800w, /_astro/visual.BbaQeyBV_Z1HpHbW.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select in a game where the item in che center is the item selected, there are icons of a d-pad on each sides howing which button to press to scroll left and right, 5 items are visible, the center item is the selected one" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Now all that’s left for us is to add that game functionality. Because as it stands now, it’s not really usable.</p> <p>Let’s recap how this works so far:</p> <ul> <li>We can open the select and use our <kbd>left</kbd> / <kbd>right</kbd> arrow to navigate the options</li> <li>We actually broke select behavior a bit because of this and our <kbd>up</kbd> and <kbd>down</kbd> arrow keys do not work anymore… this is why we need some JS.</li> </ul> <h2 id="adding-our-gamification-behavior-with-javascript">Adding our gamification behavior with JavaScript</h2> <p>First, let’s set up the variables we’ll need to track everything:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">let</span> isDragging <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">let</span> dragStartX <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">let</span> scrollStartX <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">let</span> focussedElement<span class="token punctuation">;</span> <span class="token keyword">const</span> leftArrow <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">&quot;.arrow-left&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> rightArrow <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">&quot;.arrow-right&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>The <code>isDragging</code> boolean helps us know when someone is actively dragging. The <code>dragStartX</code> and <code>scrollStartX</code> variables store where the drag started - both the mouse position and the current scroll position. We’ll need these to calculate how far to scroll as the user drags.</p> <p>The <code>focussedElement</code> variable is crucial for keyboard navigation, it keeps track of which item should be selected when the user presses <kbd>Enter</kbd>.</p> <h3 id="handling-the-drag-behavior">Handling the drag behavior</h3> <p>The drag functionality is split into three functions:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleDragStart</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> isDragging <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;dragging&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> dragStartX <span class="token operator">=</span> event<span class="token punctuation">.</span>pageX <span class="token operator">-</span> itemlist<span class="token punctuation">.</span>offsetLeft<span class="token punctuation">;</span> scrollStartX <span class="token operator">=</span> itemlist<span class="token punctuation">.</span>scrollLeft<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>When the user starts dragging, we prevent the browser’s default drag behavior (which would try to drag the element itself). We set our dragging flag to true and added a <code>.dragging</code> class to the container. This class disables scroll-snap in our CSS, so dragging feels smooth instead of snappy.</p> <p>For this, in our CSS the following is added:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.items</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous code */</span> <span class="token selector">&amp;.dragging</span> <span class="token punctuation">{</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> ew-resize<span class="token punctuation">;</span> <span class="token property">scroll-behavior</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>The <code>event.pageX - itemlist.offsetLeft</code> calculation gives us the mouse position relative to our scroll container, and <code>itemlist.scrollLeft</code> tells us where we currently are in the scroll.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleDragMove</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isDragging<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> currentX <span class="token operator">=</span> event<span class="token punctuation">.</span>pageX <span class="token operator">-</span> itemlist<span class="token punctuation">.</span>offsetLeft<span class="token punctuation">;</span> <span class="token keyword">const</span> scrollDistance <span class="token operator">=</span> <span class="token punctuation">(</span>currentX <span class="token operator">-</span> dragStartX<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span> r itemlist<span class="token punctuation">.</span>scrollLeft <span class="token operator">=</span> scrollStartX <span class="token operator">-</span> scrollDistance<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>During the drag, we calculate how far the mouse has moved from the start position. The <code>* 2</code> multiplier makes the scrolling feel faster and more responsive - without it, you’d have to drag really far to scroll a little bit.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleDragEnd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> isDragging <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">&quot;dragging&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">updateArrowVisibility</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>When the drag ends, we clean up: set the flag to <code>false</code>, remove the dragging class (which re-enables scroll-snap), and update the arrow visibility.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">let</span> isDragging <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">let</span> dragStartX <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">let</span> scrollStartX <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">handleDragStart</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> isDragging <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;dragging&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> dragStartX <span class="token operator">=</span> event<span class="token punctuation">.</span>pageX <span class="token operator">-</span> itemlist<span class="token punctuation">.</span>offsetLeft<span class="token punctuation">;</span> scrollStartX <span class="token operator">=</span> itemlist<span class="token punctuation">.</span>scrollLeft<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">handleDragMove</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isDragging<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> currentX <span class="token operator">=</span> event<span class="token punctuation">.</span>pageX <span class="token operator">-</span> itemlist<span class="token punctuation">.</span>offsetLeft<span class="token punctuation">;</span> <span class="token keyword">const</span> scrollDistance <span class="token operator">=</span> <span class="token punctuation">(</span>currentX <span class="token operator">-</span> dragStartX<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>scrollLeft <span class="token operator">=</span> scrollStartX <span class="token operator">-</span> scrollDistance<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>When dragging, we add a class that disables scroll-snap so it feels natural. When you let go, snap kicks back in.</p> <h3 id="arrow-key-navigation">Arrow key navigation</h3> <p>Since we broke the natural behavior of arrow keys in a select when using the up/down keys, we are going to re-add them using JavaScript:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleArrowKey</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>key <span class="token operator">===</span> <span class="token string">&quot;ArrowUp&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>scrollLeft <span class="token operator">-=</span> <span class="token number">40</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>key <span class="token operator">===</span> <span class="token string">&quot;ArrowDown&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span>scrollLeft <span class="token operator">+=</span> <span class="token number">40</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="the-magic-of-scrollsnapchange">The magic of scrollsnapchange</h3> <p>Here’s where things get really interesting, or rather, the magic of this demo starts. There’s a new event called <code>scrollsnapchange</code> that fires when an item snaps into position:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript">itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;scrollsnapchange&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isDragging<span class="token punctuation">)</span> <span class="token punctuation">{</span> event<span class="token punctuation">.</span>snapTargetInline<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> focussedElement <span class="token operator">=</span> event<span class="token punctuation">.</span>snapTargetInline<span class="token punctuation">;</span> <span class="token function">delayedUpdateArrowVisibility</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><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>When an item snaps to center, <code>event.snapTargetInline</code> gives us that specific element. We set focus on it so that if the user presses <kbd>Enter</kbd>, that item gets selected. We also store it in <code>focussedElement</code> for later use.</p> <p>The if (!isDragging) check is important - we don’t want to mess with focus while someone is actively dragging around.</p> <h3 id="handling-clicks-outside-the-picker">Handling clicks outside the picker</h3> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">handleOutsideClick</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>event<span class="token punctuation">.</span>target<span class="token punctuation">.</span><span class="token function">closest</span><span class="token punctuation">(</span><span class="token string">&quot;#itemlist&quot;</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>focussedElement<span class="token punctuation">)</span> <span class="token punctuation">{</span> focussedElement<span class="token punctuation">.</span>selected <span class="token operator">=</span> <span class="token boolean">true</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 we do not trigger the selection of an item on click but rather based on the center of the frame, we had to add this. If someone clicks outside the item list, we automatically select whatever item is currently focused.</p> <h3 id="managing-the-scroll-arrows">Managing the scroll arrows</h3> <p>The arrow buttons need to be disabled when you can’t scroll further in that direction:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">updateArrowVisibility</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> isAtStart <span class="token operator">=</span> itemlist<span class="token punctuation">.</span>scrollLeft <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">const</span> isAtEnd <span class="token operator">=</span> itemlist<span class="token punctuation">.</span>scrollWidth <span class="token operator">-</span> itemlist<span class="token punctuation">.</span>clientWidth <span class="token operator">&lt;=</span> itemlist<span class="token punctuation">.</span>scrollLeft<span class="token punctuation">;</span> leftArrow<span class="token punctuation">.</span>disabled <span class="token operator">=</span> isAtStart<span class="token punctuation">;</span> rightArrow<span class="token punctuation">.</span>disabled <span class="token operator">=</span> isAtEnd<span class="token punctuation">;</span> leftArrow<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">(</span><span class="token string">&quot;disabled&quot;</span><span class="token punctuation">,</span> isAtStart<span class="token punctuation">)</span><span class="token punctuation">;</span> rightArrow<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">(</span><span class="token string">&quot;disabled&quot;</span><span class="token punctuation">,</span> isAtEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We check if we’re at the very beginning (<code>scrollLeft === 0</code>) or at the end (when scrollLeft equals the maximum possible scroll distance). Then we disable the appropriate arrows and add a <code>.disabled</code> state.</p> <h3 id="the-delaying-trick-lazy-haxorz">The delaying trick (lazy haxorz)</h3> <p>Ok, this is a bit lazy on my part, I agree…</p> <p>When someone clicks an arrow or scrolls, we temporarily disable both arrows for <code>300ms</code>. This prevents rapid clicking while the smooth scroll animation is happening. The <code>300ms</code> sort of matches our CSS scroll-behavior: smooth timing, this is not an exact science…</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">delayedUpdateArrowVisibility</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> leftArrow<span class="token punctuation">.</span>disabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> rightArrow<span class="token punctuation">.</span>disabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>updateArrowVisibility<span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h3 id="centering-focused-elements">Centering focused elements</h3> <p>This function makes sure that when you open the <code>::picker(select)</code>, the focused item scrolls to the center. We calculate the center point of both the container and the focused element, then scroll by the difference.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">function</span> <span class="token function">scrollToFocusedElement</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> focussedOption <span class="token operator">=</span> document<span class="token punctuation">.</span>activeElement<span class="token punctuation">.</span>tagName <span class="token operator">===</span> <span class="token string">&quot;OPTION&quot;</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>focussedOption<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">const</span> focussedElement <span class="token operator">=</span> document<span class="token punctuation">.</span>activeElement<span class="token punctuation">;</span> <span class="token keyword">const</span> container <span class="token operator">=</span> focussedElement<span class="token punctuation">.</span><span class="token function">closest</span><span class="token punctuation">(</span><span class="token string">&quot;#itemlist&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>container<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">const</span> containerRect <span class="token operator">=</span> container<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> focussedElementRect <span class="token operator">=</span> focussedElement<span class="token punctuation">.</span><span class="token function">getBoundingClientRect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> containerCenterX <span class="token operator">=</span> <span class="token punctuation">(</span>containerRect<span class="token punctuation">.</span>left <span class="token operator">+</span> containerRect<span class="token punctuation">.</span>right<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token keyword">const</span> focussedElementCenterX <span class="token operator">=</span> <span class="token punctuation">(</span>focussedElementRect<span class="token punctuation">.</span>left <span class="token operator">+</span> focussedElementRect<span class="token punctuation">.</span>right<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token keyword">const</span> offset <span class="token operator">=</span> focussedElementCenterX <span class="token operator">-</span> containerCenterX<span class="token punctuation">;</span> container<span class="token punctuation">.</span><span class="token function">scrollBy</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">left</span><span class="token operator">:</span> offset<span class="token punctuation">,</span> <span class="token literal-property property">behavior</span><span class="token operator">:</span> <span class="token string">&quot;smooth&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h3 id="wiring-it-all-up">Wiring it all up</h3> <p>Finally, we attach all our event listeners:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript">itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;mousedown&quot;</span><span class="token punctuation">,</span> handleDragStart<span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;mousemove&quot;</span><span class="token punctuation">,</span> handleDragMove<span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;mouseup&quot;</span><span class="token punctuation">,</span> handleDragEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;mouseleave&quot;</span><span class="token punctuation">,</span> handleDragEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> itemlist<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;keydown&quot;</span><span class="token punctuation">,</span> handleArrowKey<span class="token punctuation">)</span><span class="token punctuation">;</span> document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> handleOutsideClick<span class="token punctuation">)</span><span class="token punctuation">;</span> leftArrow<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">scrollItemList</span><span class="token punctuation">(</span><span class="token string">&quot;left&quot;</span><span class="token punctuation">,</span> <span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> rightArrow<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">scrollItemList</span><span class="token punctuation">(</span><span class="token string">&quot;right&quot;</span><span class="token punctuation">,</span> <span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <h2 id="accessibility-context-is-everything-here">Accessibility: context is everything here</h2> <p>This is all about context, right? From a screen-reader perspective, I noticed that everything is still okay-ish and that there are enough ways to navigate the element. That being said, this is not the behavior a user would expect from a select, using the snap for selecting an item.</p> <p>So yes, this UI can make sense in a browser game, because in games, UI needs to be learned a bit. But for your everyday website, this might not be your accessible option as it might cause your users a bit of frustration. Context is everything!</p> <h2 id="conclusion">Conclusion</h2> <p>This demo definitely pushes the boundaries of what people expect from a select element. While it works great with keyboard navigation and screen readers, it’s not your typical form control. I’d recommend being careful about when you use something like this. It’s cool, but it might confuse users who expect a normal dropdown.</p> <p>That said, I had a blast building it, and I think it shows how creative we can get with these new CSS features. The combination of customizable select, anchor positioning, and scroll-snap creates possibilities I wouldn’t have imagined a few years ago.</p> <p>Here is that final result again:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Monster Hunter World item wheel with customizable select" src="https://codepen.io/utilitybend/embed/poMpNbg?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/poMpNbg"> Monster Hunter World item wheel with customizable select</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="other-articles-on-select">Other articles on Select:</h3> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu">The customizable select - Part five: Optgroup, creating a true select menu</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options">The customizable select - Part three: Sticky Options</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">The customizable select - Part two: Potions, anchoring, and radial shenanigans in CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">The customizable select - Part one: history, trickery, and styling the select with CSS</a></li> </ul>Brecht De RuyteCSS Day 2025, a little story by a little man on quite a big stagehttps://utilitybend.com/blog/css-day-2025-a-little-story-by-a-little-man-on-quite-a-big-stage/https://utilitybend.com/blog/css-day-2025-a-little-story-by-a-little-man-on-quite-a-big-stage/As I begin this article on the train back from CSS Day 2025, I find myself physically and mentally exhausted, yet simultaneously, I feel a glow of joy and a sense of accomplishment. This time, I’m not rushing an article about every talk. This time, I'd like to share a story about my experience at CSS Day. This article is not your rushed tech article; it’s your Sunday read, it’s a story about a little man climbing a stage he thought he’d never climb. A truthful, emotional love letter to CSS Day and a reminder that you are allowed to have a bit of fun.Fri, 13 Jun 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.DR-hDRiK_1Olzz7.avif 375w, /_astro/visual.DR-hDRiK_wICgP.avif 480w, /_astro/visual.DR-hDRiK_ZShW2T.avif 680w, /_astro/visual.DR-hDRiK_QCNkS.avif 800w, /_astro/visual.DR-hDRiK_2uUP9.avif 980w, /_astro/visual.DR-hDRiK_Z1Ti8qb.avif 1024w, /_astro/visual.DR-hDRiK_Z1KILqb.avif 1660w, /_astro/visual.DR-hDRiK_2ccdQd.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.DR-hDRiK_ZXD2Fg.webp 375w, /_astro/visual.DR-hDRiK_Z2gfYXx.webp 480w, /_astro/visual.DR-hDRiK_1nTyvE.webp 680w, /_astro/visual.DR-hDRiK_Z1VlNTu.webp 800w, /_astro/visual.DR-hDRiK_2jHroH.webp 980w, /_astro/visual.DR-hDRiK_Z2lI7jY.webp 1024w, /_astro/visual.DR-hDRiK_Z2d9KjY.webp 1660w, /_astro/visual.DR-hDRiK_Z1yAUlO.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.DR-hDRiK_UGjcR.jpg" srcset="/_astro/visual.DR-hDRiK_Z2b0AeX.jpg 375w, /_astro/visual.DR-hDRiK_1ByAgG.jpg 480w, /_astro/visual.DR-hDRiK_bx0VW.jpg 680w, /_astro/visual.DR-hDRiK_1VsLkJ.jpg 800w, /_astro/visual.DR-hDRiK_17kSP0.jpg 980w, /_astro/visual.DR-hDRiK_12oXzD.jpg 1024w, /_astro/visual.DR-hDRiK_1aXkzD.jpg 1660w, /_astro/visual.DR-hDRiK_1YwbwN.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><p>Silly, isn’t it? Some people struggle to speak in front of a group. I know a thing or two about that. No, I don’t want to overly dramatize everything, but back in 2023, I was lacking sleep for days just because I had to speak to 60 people at a local meetup. It was never my intention to be “a speaker”; I just wanted to evolve as a human being, take on a challenge, try something… different. Fastforward to 2025, a few conferences later, it has gotten a bit better. I love doing the conferences and talking to people, getting inspired by people. But this experience was a bit different, because this time I would be speaking at the conference I attended as much as possible myself… the conference I look forward to all year as an attendee.</p> <h2 id="css---long-time-to-go---day">CSS - long time to go - Day</h2> <p>The nerves didn’t change all that much, if I’m honest. Instead, the days turned into hours and then into minutes. At least for most conferences, but then…</p> <p>I remember getting the message from PPK asking me to speak at CSS Day. I recall being shocked, honored, but I wasn’t sure. Me? Really? The next thing that happened was I sent a panicky text to Bramus: “CSS Day asked me, should I do this? Will you help me?”</p> <p>A nice little nudge by him, and I answered confidently: I’ll do it! (while dying of fear inside)</p> <p>So, I prepped my slides ( a lot!) and did a few other conferences, tried to spice those up a little with snippets of my CSS Day talk, and gave a test run of the full talk at a local meetup Devs.gent. All went great, and I got some feedback on my slides from Bramus, which I fixed (mostly involved me using outdated browser icons). I was ready, I didn’t feel ready, but there wasn’t much more I could do.</p> <h3 id="two-presentations-in-two-countries-in-one-week">Two presentations in two countries in one week</h3> <p>What didn’t make things easier was doing another presentation the same week on Tuesday. Speaking at the international PHP conference by DevMio in Berlin about popovers and invokers. Berlin is a beautiful city, and I enjoyed that conference. The organisation was nice, the hotel splendid, and so was the speakers’ dinner. But I do remember that after my presentation, the only thing on my mind was getting on that plane to Amsterdam and doing the talk for CSS Day. I practiced in my hotel room in Berlin once more and then left early morning for Amsterdam.</p> <h2 id="css-pre-day">CSS Pre-Day</h2> <p>When the plane touched the Netherlands’ soil, I quickly went to the hotel. I really wanted to go to the pre-event to see the presentations by Nils Binder and Miriam Suzanne.</p> <p>After a sweaty, exhausted dash, I burst into the lobby… and then… Standing right there were Chris Coyier and John Allsopp, two absolute heroes in the web development world! I managed a “hello,” trying desperately to shove my inner, screaming fanboy back down into my gut and play it cool. First impression: Such nice people!</p> <p>Unfortunately, after dropping off my stuff, doing a quick refresh, I was a little too late for the event, and I entered the pre-event just at the end of Nils Binder’s talk. If you are reading this, Nils… I do hope to see it someday! I did pick up the presentation by Miriam. I guess that I was not the only one doing two presentations in one week. Even more so, the one Miriam did was completely different and based on an article about <a href="https://www.miriamsuzanne.com/2025/02/12/tech-ai-wtf/">tech being political</a>. It’s one of those topics that needs to be told, but the world would be better if there weren’t a need for this presentation in the first place. I believe Miriam would agree with that. I love every bit of the way this was presented with a truthful and open mindset, and without needless shouting.</p> <p>It was there that I also got to meet up with Bramus, Quentin, Amit, Roland, Jeremy, Josh, and so many others as well. And so that event ended, and we went on to…</p> <h2 id="css-boating">CSS Boating</h2> <p>You know what else gets me nervous? Boats! I have some serious motion sickness issues, which can get amplified in closed spaces. The speaker’s dinner was on a boat, and I’ll be completely honest, the first 20 minutes felt really bad. But thanks to some chats (especially with Nils, thank you, you calmed me in some way), I was able to enjoy a very nice dinner and a few drinks. I met all the speakers there and the organisation, but also some other heroes such as Una Kravets, Julia Miocene, Penelope Mclachlan, and Michelle Barker. You can tell it from the boat as well, the CSS world has a really beautiful blend of different nationalities and genders. Yes, it can be better in various ways, but I do think there is a strong foundation here, and more tech events should be like this.</p> <picture> <source srcset="/_astro/boating.CL3k3eif_2hGzJg.avif 320w, /_astro/boating.CL3k3eif_134GoA.avif 480w, /_astro/boating.CL3k3eif_ZHbjRL.avif 800w, /_astro/boating.CL3k3eif_gb4fI.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/boating.CL3k3eif_1PgAPs.webp 320w, /_astro/boating.CL3k3eif_ADHuM.webp 480w, /_astro/boating.CL3k3eif_Z19BiLz.webp 800w, /_astro/boating.CL3k3eif_ZbeTD5.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/boating.CL3k3eif_HzOGg.jpg" srcset="/_astro/boating.CL3k3eif_9cxV9.jpg 320w, /_astro/boating.CL3k3eif_Z15pkow.jpg 480w, /_astro/boating.CL3k3eif_2evM83.jpg 800w, /_astro/boating.CL3k3eif_Z1RiWxo.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A bunch of people inside of a canal boat in Amsterdam having a chat" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="112"> </picture> <p>I thought the hardest part of the day was still to come; my talk was going to be the next day. I wouldn’t possibly be able to sleep. But guess what, after a plane, the rush, the “boatstress”, I went to bed and almost 8 hours later…</p> <h2 id="css-d-day">CSS D-Day</h2> <p>There was a bit of sunlight in my beautiful princess room, and I felt the comfy sheets on top of me as a nice hug. While I slowly awakened, I did a stretch and here are my first thoughts of the morning:</p> <p>O wow, I slept fantastically… wait, where am I now? Right, Amsterdam. So nice to see everyone yesterday, that was so cool. Oh, and I didn’t get sick, what was I even worried about, I’m such a tool haha…. So, I need to speak today…</p> <p><strong style="text-transform: uppercase;">Fuuuuuuck, it’s today! shit shit shit!</strong> <br>(and so on… sorry for the language readers, I don’t usually do this, but this is the truth)</p> <p>And there it was, I never felt so awake, so fast without a drop of coffee. I was so stressed that I even misread the starting time of the conference and came way too early. Luckily, Bramus was there setting up the Google CSS Help desk, so I could nervously annoy him a bit while he was setting things up.</p> <p>But then it hit me, calm… out of nowhere, I felt calm again. It’s crazy how the human brain goes. During the whole morning, watching great talks by Adam, John, and Miriam, I constantly felt ups and downs, and I had no idea how this would turn out.</p> <p>Noon struck, my belly filled with one croissant and 4 spoons of rice, a quick laptop and soundcheck done, it was almost time for me to go on. First, there was a presentation by Cyd Stumpel, who creates some amazing animations and designs on the web. But truth be told, even though that talk was really good, it will be the one I’ll have to re-watch later on; My focus was shifting towards presenting. Sorry about that, Cyd, I’m sure she’ll understand.</p> <p>So here we go, standing next to that stage, microphone on, and these are my thoughts:</p> <p>Will I be breathing too loudly? Ok, Cyd is coming down with a big smile! I hope I’ll feel the same way. Oh, here we go, let’s set the laptop up. Stephen is starting to announce me, he’s such a great guy… Wow, he pronounced my name like a pro, that’s crazy! The announcement is done, my heart is racing, people start clapping, and then this happened:</p> <picture> <source srcset="/_astro/ocean.D8yF_y3Q_ZJW16e.avif 320w, /_astro/ocean.D8yF_y3Q_105TVC.avif 480w, /_astro/ocean.D8yF_y3Q_1VCNbT.avif 800w, /_astro/ocean.D8yF_y3Q_1RObpp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/ocean.D8yF_y3Q_szDhX.webp 320w, /_astro/ocean.D8yF_y3Q_2dCzkO.webp 480w, /_astro/ocean.D8yF_y3Q_Z1U1FdP.webp 800w, /_astro/ocean.D8yF_y3Q_Z1XPi0k.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/ocean.D8yF_y3Q_1B00Jg.jpg" srcset="/_astro/ocean.D8yF_y3Q_qdgyy.jpg 320w, /_astro/ocean.D8yF_y3Q_2bgcBp.jpg 480w, /_astro/ocean.D8yF_y3Q_Z1Wo2Wf.jpg 800w, /_astro/ocean.D8yF_y3Q_Z21cEIJ.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="An ocean with a seagull flying over it" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Calmth…</p> <p>I was there, no turning back, I told my story as I wanted to tell it, a few misspoken lines, a few mispronunciations, carefully looking if people were nodding, maybe having a little laugh with my dumb jokes. Looking for familiar faces (not finding Una, who was staring straight at me, I heard afterwards). Right in front of me, a whole line of people, listening to me, while I feel I should be listening to them. A lot went through my mind while speaking, but I had found my pace, my calm, dare I even say, my fun?</p> <p>I think I told my story about the customizable select, from how you can use it today as a progressive enhancement to some of the more experimental ideas I created with it. Showing how those new CSS capabilities can work together with this new feature. While also reminding people to have a bit of fun.</p> <picture> <source srcset="/_astro/me-presenting.DqhFmrCp_23pgGK.avif 320w, /_astro/me-presenting.DqhFmrCp_Z2sM0wY.avif 480w, /_astro/me-presenting.DqhFmrCp_Zec50U.avif 800w, /_astro/me-presenting.DqhFmrCp_OExWS.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/me-presenting.DqhFmrCp_Z1C5gdH.webp 320w, /_astro/me-presenting.DqhFmrCp_Z145oDv.webp 480w, /_astro/me-presenting.DqhFmrCp_1auvRy.webp 800w, /_astro/me-presenting.DqhFmrCp_2em9Qm.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/me-presenting.DqhFmrCp_Z1UaGlR.jpg" srcset="/_astro/me-presenting.DqhFmrCp_vbN9N.jpg 320w, /_astro/me-presenting.DqhFmrCp_14bEJ0.jpg 480w, /_astro/me-presenting.DqhFmrCp_Z1LpxxR.jpg 800w, /_astro/me-presenting.DqhFmrCp_ZHxTz4.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Me presenting, above me there is a screen with a button on it saying fire popover" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>So, I do have a lot of nerves, but at the same time, I don’t feel impostor syndrome, even though it might sound like it. I know I did the work, I did follow the meetings, created those demo’s and have a passion for it that can’t be faked. But still, the idea of standing in front of so many people, you want to give them value for their money, you want them to learn something, as well as maybe bring them a little bit of joy. And I truly care about that, and from the moment I would stop caring, I’d better stop doing this. This is important every time… Whether it’s a 20 people meetup or a filled CSS Church. I also learned that people can tell if you prepared something very well, and that an audience is usually more cheerful to a nervous person who came prepared instead of a confident person who just flung some slides together. And that’s the cool thing about CSS Day… There is always so much quality!</p> <p>I was followed up by Rachel Andrew and later by Brad and Ian Frost. All those names are just stellar, I can’t believe I was there on a stage, even now, it still feels like a dream.</p> <p>After that, we had some food, got a couple of drinks. I even met someone who came from Japan for CSS Day, can you believe it?</p> <p>The next night, I didn’t sleep at all; I was still processing this day. Dead tired, I went on the next day… Coffee helps.</p> <h2 id="css-hacking-the-other-scary-moment">CSS Hacking (the other scary moment)</h2> <p>So, I might do a blog post on the talks, because this day was completely filled with fantastic presentations. But on day two, I wanted to highlight a funny moment that shows how cool CSS Day can be, and at the same time, it was probably the scariest thing for me besides presenting.</p> <p>While sitting next to Chris Coyier, I noticed he was hacking in a CodePen, and I quickly noticed it was a customizable select! So I asked what he was doing. Chris wanted to create some stagger effect and was trying some things out. There was currently a break going on, so I took my laptop and started hacking on it next to him. A few seconds later, Una jumped into the party. So here we are, trying to hack on that select, with 3 people. I’m not going to spoil it, but it was a cool effect, and suddenly I got a little breakthrough on how we could manage it. And then it happened, what started as 3 devs doing a little competition on hacking a select turned quickly into a collaboration with me on a laptop trying to understand what Chris is going for, and Una standing behind me screaming out of enthusiasm: YOU FORGOT THE SEMICOLON!</p> <p>I had no idea you could have such a nerve-wrecking and beautiful moment at the same time! It might not read as nice as the moment was, but this is one of those characteristics of Una that I think is just awesome. When she gets excited about a breakthrough, you’ll know it.</p> <p>So many lovely chats… so many lovely people. Still gotta work on my selfie skills…</p> <picture> <source srcset="/_astro/collage.DHqOuE94_Byeky.avif 320w, /_astro/collage.DHqOuE94_ZAyoWN.avif 480w, /_astro/collage.DHqOuE94_Z2v6yFU.avif 800w, /_astro/collage.DHqOuE94_Z1htBDB.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/collage.DHqOuE94_Z1NKf9D.webp 320w, /_astro/collage.DHqOuE94_23jflV.webp 480w, /_astro/collage.DHqOuE94_8L5CO.webp 800w, /_astro/collage.DHqOuE94_1mo2F8.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/collage.DHqOuE94_1fR6SF.jpg" srcset="/_astro/collage.DHqOuE94_ZMbb9z.jpg 320w, /_astro/collage.DHqOuE94_Z20iOrV.jpg 480w, /_astro/collage.DHqOuE94_1al9CS.jpg 800w, /_astro/collage.DHqOuE94_2nX6Fc.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A collage of me taking selfies with a bunch of people at css day, smiling in each one of them" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But it shows how cool CSS Day is. How cool my heroes are and how passionate they are about the web. How welcoming they are, and supportive. What PPK created is more than a conference… It’s a mindset, a place where people might have a different culture, where people challenge each other in ideology for the web, but can still work together towards making that web a more beautiful place. This is why I went 6 times to CSS Day, it’s not just about the tech, it’s about the people as well.</p> <p>Keep supporting these conferences by buying tickets!</p> <h2 id="css-café">CSS Café</h2> <p>After a night going out for way too long, we had some CSS Café in the morning. The post-meetup where only the real nerds go to and stay for a bit longer xD</p> <p>Really, a fantastic initiative. And a very cool closing moment for CSS Day. Unfortunately, I had to leave early and wasn’t able to get the full experience. I did manage to get a preview from Julia on the talk she was going to present.</p> <picture> <source srcset="/_astro/css-cafe.eJ6Q8vVU_1C8Q3z.avif 320w, /_astro/css-cafe.eJ6Q8vVU_ZHROPE.avif 480w, /_astro/css-cafe.eJ6Q8vVU_26epF4.avif 800w, /_astro/css-cafe.eJ6Q8vVU_ZKsoYq.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/css-cafe.eJ6Q8vVU_Z28Ei9s.webp 320w, /_astro/css-cafe.eJ6Q8vVU_Av9Kf.webp 480w, /_astro/css-cafe.eJ6Q8vVU_Z1EyIwX.webp 800w, /_astro/css-cafe.eJ6Q8vVU_xUzBt.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/css-cafe.eJ6Q8vVU_h1RA5.jpg" srcset="/_astro/css-cafe.eJ6Q8vVU_1psNJa.jpg 320w, /_astro/css-cafe.eJ6Q8vVU_ZUxRa4.jpg 480w, /_astro/css-cafe.eJ6Q8vVU_1SynlE.jpg 800w, /_astro/css-cafe.eJ6Q8vVU_ZX8riP.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Julia Miocene speaking at css cafe with a slide behind her: But can you do anything useful?" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h2 id="css-day">CSS Day</h2> <p>I did wonder if I should write this story… But I think it should be heard. For those who, just like me, have a hard time scraping together the courage or just don’t know where to begin. Many of us have a story to tell, whether that is a technical web story or something completely else. And even though I’m still an absolute beginner in this, I believe it’s worth it. These few days will be locked in my heart, together with some of the greatest memories. I hope this article will help some a bit more than the AI-generated: “here are 10 tips to get over stage fright”. Because - in the end -, what you need to do… is “take the leap”.</p> <p>This was a time of joy, a rollercoaster of emotions. Not afraid to admit here that I had some tears of joy.</p> <p>To everyone involved, to everyone I chatted with, to all of those who were patient with me! For your acceptance, for your jokes, for your laughs. There is only one more thing to say.</p> <p>Thank you, and see you next year!</p> <picture> <source srcset="/_astro/next-year.8hyGagtx_2vqoEM.avif 320w, /_astro/next-year.8hyGagtx_Z10okFP.avif 480w, /_astro/next-year.8hyGagtx_ZY8y5T.avif 800w, /_astro/next-year.8hyGagtx_Z2i8Y5z.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/next-year.8hyGagtx_Z2oa5VF.webp 320w, /_astro/next-year.8hyGagtx_ZOMGtm.webp 480w, /_astro/next-year.8hyGagtx_ZNwTSq.webp 800w, /_astro/next-year.8hyGagtx_Z27xkS6.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/next-year.8hyGagtx_ZyRNjH.jpg" srcset="/_astro/next-year.8hyGagtx_19LmC1.jpg 320w, /_astro/next-year.8hyGagtx_Z2m3mIB.jpg 480w, /_astro/next-year.8hyGagtx_Z2kMA8F.jpg 800w, /_astro/next-year.8hyGagtx_1qo7FA.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="All speakers on stage at CSS day, behind them there is a slide indicating the edition next year will be 11th and 12th of june" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture>Brecht De RuyteThe customizable select - Part three: Sticky Optionshttps://utilitybend.com/blog/the-customizable-select-part-three-sticky-options/https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options/In this third episode of my exploration of the customizable select, I thought it was time to show how we can have a little bit of fun with an old friend: sticky positioning. In this article, we’ll be following up on earlier techniques while exploring how we can use the :checked pseudo-class to create a fun open and close effect. We’ll be messing with top-layer functionality, interpolate-size and other trickery to create an emoji picker.Tue, 27 May 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.DHQ7IFTZ_9fd70.avif 375w, /_astro/visual.DHQ7IFTZ_Z18mJbh.avif 480w, /_astro/visual.DHQ7IFTZ_2vMOiU.avif 680w, /_astro/visual.DHQ7IFTZ_ZNsy7e.avif 800w, /_astro/visual.DHQ7IFTZ_Z89ytc.avif 980w, /_astro/visual.DHQ7IFTZ_Z2eFoqU.avif 1024w, /_astro/visual.DHQ7IFTZ_Z2672qU.avif 1660w, /_astro/visual.DHQ7IFTZ_19WfK0.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.DHQ7IFTZ_2qrIFy.webp 375w, /_astro/visual.DHQ7IFTZ_18OLnh.webp 480w, /_astro/visual.DHQ7IFTZ_ZhbMVs.webp 680w, /_astro/visual.DHQ7IFTZ_1sIWrk.webp 800w, /_astro/visual.DHQ7IFTZ_292W5m.webp 980w, /_astro/visual.DHQ7IFTZ_2o5Ktd.webp 1024w, /_astro/visual.DHQ7IFTZ_2wE7td.webp 1660w, /_astro/visual.DHQ7IFTZ_2tlflT.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.DHQ7IFTZ_o0D8b.png" srcset="/_astro/visual.DHQ7IFTZ_5YItb.png 375w, /_astro/visual.DHQ7IFTZ_Z1bCdO6.png 480w, /_astro/visual.DHQ7IFTZ_2sxkF6.png 680w, /_astro/visual.DHQ7IFTZ_ZQI2K3.png 800w, /_astro/visual.DHQ7IFTZ_Zbp371.png 980w, /_astro/visual.DHQ7IFTZ_1bDFDB.png 1024w, /_astro/visual.DHQ7IFTZ_1kd2DB.png 1660w, /_astro/visual.DHQ7IFTZ_JXNmD.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1081" class="img-fluid"> </picture> <h2 id="quick-recap">Quick recap</h2> <p>In the previous part, I showed how we can use anchor positioning to place our picker holding our options on top of the select itself. We will be re-using this idea, but with a twist! After all, anchoring is part of <a href="https://web.dev/blog/interop-2025" target="_blank" rel="nofollow noreferrer">Interop 2025</a>, so we’d better get acquainted with it as much as possible.</p> <p>The idea for this is to use the <code>:checked</code> pseudo-class of an option to drag it right to the center to create a fun experience.</p> <p>If you have been following <a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">this series from the start</a>, you know that I like to live in a real world where I just can’t ship an unstyled select for browsers that don’t support this feature. So this example will also have somewhat of a progressive enhancement ideology.</p> <p>This is what we’ll be creating:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A select that opens horizontally and drags the checked option to the center when clicking an option"><source src="/_astro/video.t-I1UpPb.webm" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="the-setup-of-our-emoji-picker">The setup of our emoji picker</h2> <p>The HTML for this example is pretty straightforward. We won’t even need the <code>&lt;selectecontent&gt;</code> element for this one. This is the main setup:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>mood-picker<span class="token punctuation">&quot;</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">&quot;</span>inclusively-hidden<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Select a mood<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>mood-picker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>items<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</span></span>👍<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</span></span>😂<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</span></span>😍<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</span></span>😢<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>option</span><span class="token punctuation">&gt;</span></span>😡<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This is a pretty straightforward select. Note that I added a label here with the class <code>.inclusively-hidden</code>. I’m going to visually hide my label, but keep it available for screen readers. It’s perfefctly ok to keep this label visible, I’m just doing it to pretty my demo a bit. Still I wanted to keep the label there as a reminder that we should label our form elements… always.</p> <p>This is how you can do this, if you want to do the same:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.inclusively-hidden</span> <span class="token punctuation">{</span> <span class="token property">clip</span><span class="token punctuation">:</span> <span class="token function">rect</span><span class="token punctuation">(</span>0 0 0 0<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">inset</span><span class="token punctuation">(</span>50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 1px<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 property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">white-space</span><span class="token punctuation">:</span> nowrap<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 1px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Ok, our HTML is set, let’s start by creating some of the groundwork of our customizable select.</p> <p>We will be needing two colors which I will add in variables, a basic background color and a selected color. We will be transitioning to a <code>width: auto;</code>. Thanks to <code>interpolate-size: allow-keywords</code>, this has become a possibility and can be used as a progressive enhancement as well. There is a way to do this individually with <code>cal-size()</code>, but I’d like to take the lazy route for this one by adding the following to the root of my CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--selected-color</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>99% 0.44 280<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--light-color</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>90% 0.49 269<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">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This will allow us to animate from, for example, a <code>width: 64px</code> to <code>width: auto</code>. We’ll circle back to this later on, but first, let’s set up some of that basic select styling. Remember, I’m first styling for every browser before we get to the main dish:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <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 property">display</span><span class="token punctuation">:</span> flex<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 property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 64px<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">border</span><span class="token punctuation">:</span> 0<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">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> 1<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">text-align-last</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--selected-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This would currently result in the following for every browser:</p> <picture> <source srcset="/_astro/select-base.B8LA_Jy2_Z1VmPXO.avif 320w, /_astro/select-base.B8LA_Jy2_1w0Hh7.avif 480w, /_astro/select-base.B8LA_Jy2_Zun3El.avif 800w, /_astro/select-base.B8LA_Jy2_2p51VT.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-base.B8LA_Jy2_2kw9m9.webp 320w, /_astro/select-base.B8LA_Jy2_HHyN9.webp 480w, /_astro/select-base.B8LA_Jy2_Z1iFc8j.webp 800w, /_astro/select-base.B8LA_Jy2_1ALSsV.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-base.B8LA_Jy2_Zi9hVY.png" srcset="/_astro/select-base.B8LA_Jy2_vKK1z.png 320w, /_astro/select-base.B8LA_Jy2_Z162Owq.png 480w, /_astro/select-base.B8LA_Jy2_1WKxl3.png 800w, /_astro/select-base.B8LA_Jy2_ZcXuQD.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A circle button with a thumbs-up on it" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But when you click it, you get the default picker:</p> <picture> <source srcset="/_astro/select-base-open.BMVLCjY7_Z1sKsq0.avif 320w, /_astro/select-base-open.BMVLCjY7_Z1WRMQn.avif 480w, /_astro/select-base-open.BMVLCjY7_Z6TVMI.avif 800w, /_astro/select-base-open.BMVLCjY7_Z6YSDX.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-base-open.BMVLCjY7_dUs3B.webp 320w, /_astro/select-base-open.BMVLCjY7_ZgbRmL.webp 480w, /_astro/select-base-open.BMVLCjY7_1zKXFS.webp 800w, /_astro/select-base-open.BMVLCjY7_1zG1OD.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-base-open.BMVLCjY7_1VAcTd.png" srcset="/_astro/select-base-open.BMVLCjY7_Z26v1nx.png 320w, /_astro/select-base-open.BMVLCjY7_2tyM01.png 480w, /_astro/select-base-open.BMVLCjY7_ZJEuKg.png 800w, /_astro/select-base-open.BMVLCjY7_ZJJrBv.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A circle button with a thumbs-up on it, opening a picker of options with 5 emoji in it: thumbs-up, joy, love, cry, and angry" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>The only special fix here is the <code>text-align-last</code> property, which is used to center our select’s content for Safari. Ok, we got a nice and solid foundation, let’s get going to our select with sticky options.</p> <h2 id="opting-in-and-positioning-our-pickerselect">Opting in and positioning our ::picker(select)</h2> <p>First, we want to create our opt-in to the customizable select and sprinkle a nice little anchor positioning on top, to place our <code>::picker(select)</code> above the select. I am also directly hiding the <code>::picker-icon</code> pseudo-element, we don’t really want it for this demo:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token selector">&amp;::picker-icon</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> <span class="token punctuation">}</span> <span class="token comment">/* For easy tinkering, I wrap the whole custom shebang in a new feature query */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">select::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token property">inset-block</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>top<span class="token punctuation">)</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> clip<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">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--light-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 60px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>There are a few things going on here. By using the anchor functions, I am placing the picker on top of my select, clipping overflow, and giving it the same size as the select itself. <em>This is the setup for magic to happen.</em></p> <p><strong>Why do we use anchor functions for our positioning?</strong></p> <p>The <code>::picker(select)</code> is inside the top-layer, located outside of the document flow. We don’t really need to provide an <code>anchor-name</code> for this because the picker is automatically associated with the select button via CSS anchor positioning. This is why we are still using anchor functions to position the picker.</p> <p>By now, you should have something like this:</p> <picture> <source srcset="/_astro/1-select-setup.BNnV3Ofo_1NMcDl.avif 320w, /_astro/1-select-setup.BNnV3Ofo_1Nm1k3.avif 480w, /_astro/1-select-setup.BNnV3Ofo_ZXcUrx.avif 800w, /_astro/1-select-setup.BNnV3Ofo_2uHRGr.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/1-select-setup.BNnV3Ofo_Zoll2k.webp 320w, /_astro/1-select-setup.BNnV3Ofo_ZoLwlC.webp 480w, /_astro/1-select-setup.BNnV3Ofo_1SPEFI.webp 800w, /_astro/1-select-setup.BNnV3Ofo_hzk0L.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/1-select-setup.BNnV3Ofo_2aziym.png" srcset="/_astro/1-select-setup.BNnV3Ofo_Z1JNln2.png 320w, /_astro/1-select-setup.BNnV3Ofo_Z1KewGk.png 480w, /_astro/1-select-setup.BNnV3Ofo_xnEl1.png 800w, /_astro/1-select-setup.BNnV3Ofo_Z13RFjV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The circle shows the clipped picker select in the center" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Once again, notice how our options are on top of our select and completely visible. Now let’s add a bit of styling.</p> <h2 id="styling-the-picker-and-options">Styling the picker and options</h2> <p>For easy tinkering, what I would suggest is to remove the comment on our previously set <code>width</code> and <code>height</code> we’ve set on our select for now:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select::picker(select)</span> <span class="token punctuation">{</span> <span class="token comment">/* comment out these lines width: 64px; height: 64px; */</span> <span class="token punctuation">}</span> </code></pre> <p>That way, we can see our options completely:</p> <picture> <source srcset="/_astro/2-select-overflown.BY00I9Fn_14R0xX.avif 320w, /_astro/2-select-overflown.BY00I9Fn_F2lQM.avif 480w, /_astro/2-select-overflown.BY00I9Fn_Z10DYON.avif 800w, /_astro/2-select-overflown.BY00I9Fn_Z4NHtG.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/2-select-overflown.BY00I9Fn_2dstcU.webp 320w, /_astro/2-select-overflown.BY00I9Fn_1NCOvJ.webp 480w, /_astro/2-select-overflown.BY00I9Fn_7VsO9.webp 800w, /_astro/2-select-overflown.BY00I9Fn_13LKag.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/2-select-overflown.BY00I9Fn_ZEg77N.png" srcset="/_astro/2-select-overflown.BY00I9Fn_S3lwd.png 320w, /_astro/2-select-overflown.BY00I9Fn_tdGP2.png 480w, /_astro/2-select-overflown.BY00I9Fn_Z1csDQy.png 800w, /_astro/2-select-overflown.BY00I9Fn_ZgCmvr.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A list of emoji hanging on top of the select vertically" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>So let’s get to styling, first off, let us set the <code>.items</code> wrapper to flex our options:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select .items</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">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">scrollbar-width</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We are also hiding the scrollbar in this example as we don’t really want to see this peeping. The <code>overflow</code> needs to be set to <code>auto</code> for the sticky effect to happen later on.</p> <picture> <source srcset="/_astro/3-select-flexed.h_i3-D7q_Z2a5pjV.avif 320w, /_astro/3-select-flexed.h_i3-D7q_F2uVg.avif 480w, /_astro/3-select-flexed.h_i3-D7q_Z3n2VG.avif 800w, /_astro/3-select-flexed.h_i3-D7q_1RFK5R.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/3-select-flexed.h_i3-D7q_lltWj.webp 320w, /_astro/3-select-flexed.h_i3-D7q_Z1SHIAq.webp 480w, /_astro/3-select-flexed.h_i3-D7q_2s3Qky.webp 800w, /_astro/3-select-flexed.h_i3-D7q_ZG4tqO.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/3-select-flexed.h_i3-D7q_1LMifU.png" srcset="/_astro/3-select-flexed.h_i3-D7q_ZRhw14.png 320w, /_astro/3-select-flexed.h_i3-D7q_1WPof8.png 480w, /_astro/3-select-flexed.h_i3-D7q_1epPmb.png 800w, /_astro/3-select-flexed.h_i3-D7q_Z1THupc.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A list of emoji are now horizontally and still show a checkmark in front of the selected option, they are smaller than the select but all visible horizontally" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Next up is for us to add a bit of styling to our options and hide that checkmark:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</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">align-items</span><span class="token punctuation">:</span> center<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">flex</span><span class="token punctuation">:</span> 0 0 66px<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 66px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 66px<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">padding</span><span class="token punctuation">:</span> 0 1rem<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--light-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> background 0.4s<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 selector">&amp;::checkmark</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> <p>We are mainly just setting our options to be full height and set in the middle. I did however notice some pixel rounding issues from time to time, so I decided to make them a bit higher than the picker, because the overflow is clipped anyway, that’s perfectly fine here. Since the example works with rounded corners, we will also need to give our first and last-child a bit of a <code>border-radius</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous code */</span> <span class="token selector">&amp;:first-child</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 60px 0 0 60px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:last-child</span> <span class="token punctuation">{</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0 60px 60px 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>We currently see how we want our select to be when expanded:</p> <picture> <source srcset="/_astro/4-select-opened.B6YhjjGH_ZPQ7Qk.avif 320w, /_astro/4-select-opened.B6YhjjGH_1YgMoR.avif 480w, /_astro/4-select-opened.B6YhjjGH_1fQevU.avif 800w, /_astro/4-select-opened.B6YhjjGH_Z1Sh6fs.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/4-select-opened.B6YhjjGH_1EzLpU.webp 320w, /_astro/4-select-opened.B6YhjjGH_Zztr7O.webp 480w, /_astro/4-select-opened.B6YhjjGH_Z1iT00L.webp 800w, /_astro/4-select-opened.B6YhjjGH_C9N1M.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/4-select-opened.B6YhjjGH_Z1Yay5p.png" srcset="/_astro/4-select-opened.B6YhjjGH_qVKrx.png 320w, /_astro/4-select-opened.B6YhjjGH_Z1N7s6c.png 480w, /_astro/4-select-opened.B6YhjjGH_Z2wx0Y9.png 800w, /_astro/4-select-opened.B6YhjjGH_ZAtcVA.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The picker is now horizontally hanging on the left side of the selet in a pill sort of shape" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Now let’s uncomment the width and height of our picker again:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">select::picker(select)</span> <span class="token punctuation">{</span> <span class="token comment">/* uncomment out these lines */</span> <span class="token property">width</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="creating-the-open-effect-and-sticky-dragging-of-the-checked-option">Creating the open effect and sticky dragging of the checked option</h2> <p>We haven’t created an animation for our select to open up. I have some thoughts on this, so do read on after the code blocks.</p> <p>Let’s update our picker to have a transition on width:</p> <div class="alert alert-warning"><p>Note: Currently, there seems to be an issue on touch devices. There is a workaround for now by setting <code>pointer-events</code> only to trigger when the select is fully open. I will keep this article updated for progress on that part, and will also inform on my social when the issue would be fixed. So, currently the pointer-events hack is added.</p></div> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select::picker(select)</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous code */</span> <span class="token property">transition</span><span class="token punctuation">:</span> width 0.5s ease-out<span class="token punctuation">,</span> display 0.5s<span class="token punctuation">,</span> overlay 0.5s<span class="token punctuation">,</span> pointer-events 0.5s<span class="token punctuation">;</span> <span class="token property">transition-behavior</span><span class="token punctuation">:</span> allow-discrete<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Let’s also set the final state of our open select and the <code>@starting-style</code></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select:open::picker(select)</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">width</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">pointer-events</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 64px<span class="token punctuation">;</span> <span class="token property">pointer-events</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Because we’ve set <code>interpolate-size: allow-keywords</code> on our <code>:root</code>, we can transition from a fixed width to an auto width. Since the picker is inside the top-layer we have to set <code>transition-behavior: allow-discrete</code> to transition the <code>overlay</code> and <code>display</code> property, and use <code>@starting-style</code> for when that picker is opened.</p> <p>Ok, our select opens smoothly and so the last piece of the puzzle is to add <code>position: sticky</code> to the <code>:checked</code> option</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token selector">&amp;:checked</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--selected-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inset-block</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">inset-inline-end</span><span class="token punctuation">:</span> 0<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> <span class="token punctuation">}</span> </code></pre> <p>We now have our “sticky options” effect, and I think it’s pretty cool that we can do this!</p> <p>In the final demo, I also updated some hover animations for the options and some alternative on smaller window sizes. Do check it out:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Customizable select with sticky option" src="https://codepen.io/utilitybend/embed/MWdaMxm?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/MWdaMxm"> Customizable select with sticky option</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>At the current time, the demo seems to have some issues on mobile devices, something to look at for sure.</p> <h2 id="some-thoughts">Some thoughts</h2> <p>I would never consider myself an accessibility expert, although sometimes I get the feeling I know more than some “experts” (If you’re reading my blog, chances are high you’re not the kind of person I’m referring to). But after some quick testing I did notice small nuances on how VoiceOver handles the menu items, sometimes I get a count (eg: item 1 of 5), sometimes I only get a general “5 items”. And this is why I’m not an expert, I test this, I notice the difference, but what is the real impact here? I think it’s an honest question. A user can surely navigate this example, and I think it makes sense, but inconsistencies are never a good thing. So, I’m certainly not red-flagging this as an accessibility hazard, but still, something to keep in mind.</p> <p>Besides this concern… To quote <a href="https://io.google/2025/explore/pa-keynote-15" target="_blank" rel="nofollow noreferrer">Una recently at Google I/O</a>, we are entering a golden age of UI.</p> <p>It’s so cool that we can create these little fun experiences.</p> <h3 id="other-articles-on-select">Other articles on Select:</h3> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu">The customizable select - Part five: Optgroup, creating a true select menu</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification">The customizable select - Part four: Scroll snapping, state queries, monster hunter, and gamification</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">The customizable select - Part two: Potions, anchoring, and radial shenanigans in CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">The customizable select - Part one: history, trickery, and styling the select with CSS</a></li> </ul>Brecht De RuyteOklchroma: an OKLCH color pattern generator that generates CSS variableshttps://utilitybend.com/blog/oklchroma-an-oklch-color-pattern-generator-that-generates-css-variables/https://utilitybend.com/blog/oklchroma-an-oklch-color-pattern-generator-that-generates-css-variables/Sometimes you get inspired by a presentation or an article you’ve read. For me, an inspiration started at CSS Day last year during the opening keynote by Mathias Ott. One of those side projects I kept placing at the bottom of the pile, until I finally had had enough of it. I had to make it. In this article, I’d like to present a little tool, an OKLCH color picker. A tool created by me using CSS, a bit of React and a bit of AI help.Thu, 08 May 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.Dc6F2Elg_NCqyi.avif 375w, /_astro/visual.Dc6F2Elg_ZsYvIY.avif 480w, /_astro/visual.Dc6F2Elg_Z1T163I.avif 680w, /_astro/visual.Dc6F2Elg_Z95kEV.avif 800w, /_astro/visual.Dc6F2Elg_ZXddaF.avif 980w, /_astro/visual.Dc6F2Elg_2jEhRW.avif 1024w, /_astro/visual.Dc6F2Elg_2sdDRW.avif 1660w, /_astro/visual.Dc6F2Elg_1kWvlp.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.Dc6F2Elg_Z1YmbG5.webp 375w, /_astro/visual.Dc6F2Elg_1NcYOz.webp 480w, /_astro/visual.Dc6F2Elg_nbpuP.webp 680w, /_astro/visual.Dc6F2Elg_287aSC.webp 800w, /_astro/visual.Dc6F2Elg_1iYinS.webp 980w, /_astro/visual.Dc6F2Elg_1ReiY9.webp 1024w, /_astro/visual.Dc6F2Elg_20MEY9.webp 1660w, /_astro/visual.Dc6F2Elg_Z2pPCQC.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.Dc6F2Elg_Z7PNlS.png" srcset="/_astro/visual.Dc6F2Elg_KmVUt.png 375w, /_astro/visual.Dc6F2Elg_Zwf0mN.png 480w, /_astro/visual.Dc6F2Elg_Z1WgzGx.png 680w, /_astro/visual.Dc6F2Elg_ZckOiK.png 800w, /_astro/visual.Dc6F2Elg_Z11sGNu.png 980w, /_astro/visual.Dc6F2Elg_EMe9x.png 1024w, /_astro/visual.Dc6F2Elg_NlA9x.png 1660w, /_astro/visual.Dc6F2Elg_UY3X3.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><p>If you are wondering about <a href="https://www.youtube.com/watch?v=su6WA0kUUJE" target="_blank" rel="noreferrer noopener">the presentation by Mathias Ott that inspired this, take a look here</a>.</p> <h2 id="naming-my-picker">Naming my picker</h2> <p>I don’t have anything against Oklahoma, and I do hope naming this tool didn’t offend anyone. But if you are wondering for the rest of this article, how do I pronounce this thing - well, it’s “Oak-ul-kroh-muh.”. Naming things is hard…</p> <a href="https://oklchroma.utilitybend.com/" target="_blank" rel="noreferrer noopener">Here you can see the Oklchroma tool in action</a> <picture> <source srcset="/_astro/oklchroma-screenshot.BcUX5IUH_Z28GG7y.avif 320w, /_astro/oklchroma-screenshot.BcUX5IUH_Zz73RD.avif 480w, /_astro/oklchroma-screenshot.BcUX5IUH_2eUiuA.avif 800w, /_astro/oklchroma-screenshot.BcUX5IUH_Z9zfj6.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/oklchroma-screenshot.BcUX5IUH_Y2Egh.webp 320w, /_astro/oklchroma-screenshot.BcUX5IUH_Z2wyQiJ.webp 480w, /_astro/oklchroma-screenshot.BcUX5IUH_hsv4u.webp 800w, /_astro/oklchroma-screenshot.BcUX5IUH_Z2722Jc.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/oklchroma-screenshot.BcUX5IUH_Zhz8YA.jpg" srcset="/_astro/oklchroma-screenshot.BcUX5IUH_Z7W2hK.jpg 320w, /_astro/oklchroma-screenshot.BcUX5IUH_1qCzWa.jpg 480w, /_astro/oklchroma-screenshot.BcUX5IUH_ZOwbtx.jpg 800w, /_astro/oklchroma-screenshot.BcUX5IUH_1QaovH.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A screenshot of the tool whrere you can see the oklchroma title and some color inputs with range sliders" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>I won’t be going into code details on how I created this tool, mostly because I am in no way React-savy enough to be an educator on the matter. But if you are wondering, yes, the sliders, controls, everything except for the color magic itself, is React / Typescript. <a href="https://github.com/brechtDR/oklchroma" target="_blank" rel="noreferrer noopener">The repository is public if you want to see some bloated non-best-practice code</a>.</p> <h2 id="the-idea-for-an-oklch-pattern-generator">The idea for an OKLCH pattern generator</h2> <p>The idea of this color picker is to have one color input that is then thrown into some calculations to create a color pattern. I believe a handpicked color pattern will always be far more superior to a mathematically generated one. It’s pretty neat that we can do this with CSS nowadays. The example shown in the presentation came in two-fold. First, Mathias stated that generating a pattern could be handled like this with CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--primary</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>56.6% 0.27 274<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-10</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 10% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-20</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 20% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-30</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 30% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-40</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 40% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-50</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 50% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-60</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 60% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-70</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 70% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-80</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 80% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-90</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 90% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-100</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 100% c h<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Basic explanation of this syntax: The <code>oklch()</code> color function starts with an input color, which is specified right after the “from” keyword. Based on that input color, we can create a new output by adjusting the Lightness, Chroma, and Hue. In this first example, the lightness of the color changed.</p> <picture> <source srcset="/_astro/oklch-base.D1OnR8rd_2rz1li.avif 320w, /_astro/oklch-base.D1OnR8rd_Z1WMsbY.avif 480w, /_astro/oklch-base.D1OnR8rd_Z2jNUxy.avif 800w, /_astro/oklch-base.D1OnR8rd_Z1h47ae.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/oklch-base.D1OnR8rd_Z2kckVP.webp 320w, /_astro/oklch-base.D1OnR8rd_Z1EmFFb.webp 480w, /_astro/oklch-base.D1OnR8rd_Z21o91K.webp 800w, /_astro/oklch-base.D1OnR8rd_ZXDkDq.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/oklch-base.D1OnR8rd_KJCWQ.png" srcset="/_astro/oklch-base.D1OnR8rd_ykKlE.png 320w, /_astro/oklch-base.D1OnR8rd_1eapCj.png 480w, /_astro/oklch-base.D1OnR8rd_R8WgJ.png 800w, /_astro/oklch-base.D1OnR8rd_1TSKE4.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pattern of 10 colors ranging from dark to light blue against a gray background" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But that was not the mind-blowing part. The colors generated in this first example do look a little bit flat. Usually, what we see in color patterns is that we can see some sort of curve, where, for example, the dark colors are really dark and the light ones are almost pale, while the middle is very vibrant. What happened next was really smart. By using a base value and the trigonometric sine() function, Matthias showed some sort of easing curve that would adjust the Chroma of the color.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--primary</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>56.6% 0.27 270<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--c-base</span><span class="token punctuation">:</span> 0.05<span class="token punctuation">;</span> <span class="token property">--primary-10</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span> from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 10% <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-base<span class="token punctuation">)</span> + <span class="token punctuation">(</span><span class="token function">sin</span><span class="token punctuation">(</span>1 * pi<span class="token punctuation">)</span> * c<span class="token punctuation">)</span><span class="token punctuation">)</span> h <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-20</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span> from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 20% <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-base<span class="token punctuation">)</span> + <span class="token punctuation">(</span><span class="token function">sin</span><span class="token punctuation">(</span>0.9 * pi<span class="token punctuation">)</span> * c<span class="token punctuation">)</span><span class="token punctuation">)</span> h <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-30</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span> from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 30% <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-base<span class="token punctuation">)</span> + <span class="token punctuation">(</span><span class="token function">sin</span><span class="token punctuation">(</span>0.8 * pi<span class="token punctuation">)</span> * c<span class="token punctuation">)</span><span class="token punctuation">)</span> h <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--primary-40</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span> from <span class="token function">var</span><span class="token punctuation">(</span>--primary<span class="token punctuation">)</span> 40% <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--c-base<span class="token punctuation">)</span> + <span class="token punctuation">(</span><span class="token function">sin</span><span class="token punctuation">(</span>0.7 * pi<span class="token punctuation">)</span> * c<span class="token punctuation">)</span><span class="token punctuation">)</span> h <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/oklch-sin.Bw7Zj3Hg_1TPBNm.avif 320w, /_astro/oklch-sin.Bw7Zj3Hg_Z1AY7xg.avif 480w, /_astro/oklch-sin.Bw7Zj3Hg_Z1zIkWk.avif 800w, /_astro/oklch-sin.Bw7Zj3Hg_2bsmQV.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/oklch-sin.Bw7Zj3Hg_25rg0P.webp 320w, /_astro/oklch-sin.Bw7Zj3Hg_Z1qntkM.webp 480w, /_astro/oklch-sin.Bw7Zj3Hg_Z1p7GJQ.webp 800w, /_astro/oklch-sin.Bw7Zj3Hg_2m414p.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/oklch-sin.Bw7Zj3Hg_Z2kCbmF.png" srcset="/_astro/oklch-sin.Bw7Zj3Hg_ZAX0pW.png 320w, /_astro/oklch-sin.Bw7Zj3Hg_Woo2m.png 480w, /_astro/oklch-sin.Bw7Zj3Hg_XEaCi.png 800w, /_astro/oklch-sin.Bw7Zj3Hg_Zklfmn.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pattern of 10 colors ranging from dark to light blue against a gray background, but the colors are much more vivid in the center, darker at the start and lighter at the end compare to the previous image" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p><strong>Note: What is Chroma?</strong><br>In short, the Chroma is not a percentage, it is a value between <code>0</code> and <code>0.37</code>. This value represents the vividness of the color.</p> <p>I thought this was a great idea, and I quickly started wondering how this would work with other color inputs, or how about absolutely mental base modifiers that extend the Chroma way beyond what’s normally possible? Because theoretically, the vividness of a color goes to infinity. I played around with it a few times, but never really created a tool for it at that point. But finally, after a long time, I started planning:</p> <h2 id="must-haves">Must-haves</h2> <p>I wanted to re-create the output from the presentation, but also with a twitst. The primary objective was for people to easily copy the output.</p> <p>My must-haves for this picker were the following:</p> <ul> <li>Multiple color syntaxes as input</li> <li>A way to easily copy the output CSS</li> <li>Since I want an easy output, a way to rename the variables easily would be nice</li> </ul> <h2 id="want-to-haves">Want-to-haves</h2> <p>This already seemed a cool thing, but I wondered: Can I actually turn this into a tool that I would want to use when creating a quick demo? Maybe even better, a tool that someone else wants to use from time to time. So then I thought, wouldn’t it be cool to create a system where people could share the pattern they created, or how about creating a full-fledged system.</p> <ul> <li>Make people add multiple patterns</li> <li>Make it so that url changes and there is a share link for it.</li> <li>Make color inputs more fancy by color updating range sliders (Check the sRGB one as reference)</li> <li>Light and dark theme!</li> </ul> <p>They weren’t in my first prototype, but as I’m very late on doing a write-up about this, I can say they have been added. To be completely fair, this required me to go beyond my usual React skills and had a bit of help from a friend (an AI friend).</p> <p>This is the part where it all became a bit “icky” for me. My skills were too few, and to be fair, I have a whole bunch of other things that I really want to learn before polishing this. The code became a bit more out of my control. So I decided to leave it there before going more into that rabbit hole.</p> <p>While creating this, I added another feature that could be done with CSS in the future, changing the color of text of tab based on the picked color, while at the same time have a good color contrast, there are a few hacks for this already, but I kinda want to wait until we have <a href="https://www.w3.org/TR/css-color-5/#contrast-color" target="_blank" rel="noreferrer noopener">the real thing</a>, which brings me to the next thing…</p> <h2 id="future-additions">Future additions</h2> <p>There are a few additions that I want to create. For example, I love that we can crank-up the Chroma beyond 0.37, but I should at least give a little warning in those cases that results can vary.</p> <ul> <li>Add a warning when Chroma exceeds <code>0.37</code> based on the base-modifier</li> <li>Add more CSS features to replace the JS I’ve written (such as the contrast checker)</li> <li>Maybe do something with CSS functions and Mixins when that comes along?</li> <li>Clean it up a bit</li> <li>Maybe….just maybe give it a custom domain?</li> </ul> <p>That’s pretty much it. I wonder what you think of it? It’s not often that I create little tools like this, but with my CSS knowledge, basic React skills, and a little bit of help from an AI friend, I was able to do this quite easily. It felt like the perfect combination regarding the amount of AI that went into it, just enhancing my capabilities as a developer instead of just basic “vibe coding”. But let’s not go into that debate right now.</p> <p>Happy color picking!</p>Brecht De RuyteThe customizable select - Part two: Potions, anchoring, and radial shenanigans in CSShttps://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css/https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css/In this second part of my customizable select series, I’d like to highlight one feature from Interop 2025 that works beautifully with this feature: anchor positioning. This article contains one of my first demos ever created when styling the select and has evolved with every new syntax and iteration across the months / years. We'll also be taking a glimpse at a few experimental features, but the basis of this can be used today. Let’s take a look at my potion selector with CSS.Tue, 15 Apr 2025 00:00:00 GMT<picture> <source srcset="/_astro/select-done.TGNuopXR_ZQUoBw.avif 375w, /_astro/select-done.TGNuopXR_Z11XvW5.avif 480w, /_astro/select-done.TGNuopXR_pXlCN.avif 680w, /_astro/select-done.TGNuopXR_GHD5A.avif 800w, /_astro/select-done.TGNuopXR_Z1SHzVb.avif 980w, /_astro/select-done.TGNuopXR_Z2r6kLe.avif 1024w, /_astro/select-done.TGNuopXR_1r6n07.avif 1660w, /_astro/select-done.TGNuopXR_1hA5F5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/select-done.TGNuopXR_Z1Fdx5u.webp 375w, /_astro/select-done.TGNuopXR_Z1PgEq3.webp 480w, /_astro/select-done.TGNuopXR_ZnjLPa.webp 680w, /_astro/select-done.TGNuopXR_Z6zunn.webp 800w, /_astro/select-done.TGNuopXR_2nbpoM.webp 980w, /_astro/select-done.TGNuopXR_Z24jUox.webp 1024w, /_astro/select-done.TGNuopXR_1NRMmN.webp 1660w, /_astro/select-done.TGNuopXR_Z2nTrfn.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/select-done.TGNuopXR_Z1UmcdL.png" srcset="/_astro/select-done.TGNuopXR_1AdcnR.png 375w, /_astro/select-done.TGNuopXR_1qa53j.png 480w, /_astro/select-done.TGNuopXR_Z2c5baJ.png 680w, /_astro/select-done.TGNuopXR_Z1UkSHW.png 800w, /_astro/select-done.TGNuopXR_yq14d.png 980w, /_astro/select-done.TGNuopXR_Z2pHGQ3.png 1024w, /_astro/select-done.TGNuopXR_1su0Ui.png 1660w, /_astro/select-done.TGNuopXR_1YARdF.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1918" height="1080" class="img-fluid"> </picture> <p>In <a href="(/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css)">the first part of this series</a>, I never really said how many parts this series is going to be. To be honest, I don’t really know myself. Between the first article and now, I already had the inspiration for another article, this select capability is just so cool…</p> <p>So, there will undoubtedly be parts 3 and 4, and I’m guessing a part 5 is also cooking somewhere in my brain. This part will be about a single demo, a radial select, and a potion picker with CSS. I don’t want to go back into history as I did in the first article, but it is worth mentioning that this is one of the very first demos I created with the customizable select, back when the initial idea was still a new element. However, I do believe that this demo also had a bit of a helpful contribution to the end product we have now, as it did expose some difficulties to browser engineers regarding animating options that overlap the select button while clicking. Look at me talking like I make a big difference… but really, I just loved experimenting and I’m happy it helped, even if it’s just that tiny little bit.</p> <h2 id="css-anchor-positioning-and-the-customizable-select">CSS Anchor positioning and the customizable select</h2> <p>CSS Anchor positioning is one of those features that I’m really excited about, what’s even more fantastic, is that it’s part of Interop 2025. I won’t be going into the full syntax of anchoring but will focus on some trickery specifically for this use case. (<a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">I also have written on anchor positioning before</a>)</p> <h3 id="where-we-left-off">Where we left off…</h3> <p>In the <a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">previous article</a>, we ended up with images inside of our options and used the new <code>&lt;selectecontent&gt;</code> element to show the checked option’s contents. We are going to keep building on this idea. This is what we’ll be making in the end:</p> <picture> <source srcset="/_astro/select-done.TGNuopXR_21n7Nv.avif 320w, /_astro/select-done.TGNuopXR_oyxfv.avif 480w, /_astro/select-done.TGNuopXR_Z1BOdFW.avif 800w, /_astro/select-done.TGNuopXR_1hCQUi.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-done.TGNuopXR_1d4Ykx.webp 320w, /_astro/select-done.TGNuopXR_ZoIAds.webp 480w, /_astro/select-done.TGNuopXR_Z2q7m9U.webp 800w, /_astro/select-done.TGNuopXR_tkIrk.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-done.TGNuopXR_Z1pArXA.png" srcset="/_astro/select-done.TGNuopXR_ZAFp02.png 320w, /_astro/select-done.TGNuopXR_Z2dtYy2.png 480w, /_astro/select-done.TGNuopXR_Pjnjr.png 800w, /_astro/select-done.TGNuopXR_Z1kpESf.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>For the rest of this article, I am going to presume you are already familiar with the intro on the customizable select. Ready? Let’s get to it!</p> <h2 id="the-html-of-our-potion-selector">The HTML of our potion selector</h2> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>items<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>health<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>potion-holder<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>icon icon-health<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>#potion<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>Health<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>mana<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>potion-holder<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>icon icon-mana<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>#potion<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>Mana<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- and the options in this trend.. --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This is looking pretty much the same as the things we did previously. We have our <code>&lt;select&gt;</code> and inside of it, we added a <code>&lt;button&gt;</code> containing our <code>&lt;selectedcontent&gt;</code> element. Quick recap: The <code>&lt;selectedcontent&gt;</code> element will hold the full content of the current selected <code>&lt;option&gt;</code>.</p> <p>Inside of every option, we’ll add an SVG that holds the image of a flask as well as a <code>&lt;span&gt;</code> containing the text of that option. Please note, that I’m adding <code>aria-hidden=&quot;true&quot;</code> to these SVG’s as they are purely decorative in this example. The main difference here is that there is an extra wrapper around the options with the <code>.item</code> class, more on that later on.</p> <h2 id="the-basic-setup-in-css---variables-and-basic-select-styles">The basic setup in CSS - variables and basic select styles</h2> <p>The first thing on the agenda for our custom select is to create some variables and make sure that we include this as a progressive enhancement. In my <code>:root</code> variables, I also did a bit of default styling to my body background, but I’m not covering this to stay on point. Here are the color names I used and the sizing:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token comment">/* colors */</span> <span class="token property">--color-space</span><span class="token punctuation">:</span> #282f44<span class="token punctuation">;</span> <span class="token property">--color-dark-liver</span><span class="token punctuation">:</span> #453a49<span class="token punctuation">;</span> <span class="token property">--color-liver</span><span class="token punctuation">:</span> #634570<span class="token punctuation">;</span> <span class="token property">--color-catawba</span><span class="token punctuation">:</span> #6d3b47<span class="token punctuation">;</span> <span class="token property">--color-catawba-light</span><span class="token punctuation">:</span> #8c516c<span class="token punctuation">;</span> <span class="token property">--color-lightest</span><span class="token punctuation">:</span> #fafafa<span class="token punctuation">;</span> <span class="token comment">/* sizing */</span> <span class="token property">--orb-size</span><span class="token punctuation">:</span> 110px<span class="token punctuation">;</span> <span class="token property">--option-size</span><span class="token punctuation">:</span> 80px<span class="token punctuation">;</span> <span class="token property">--circle-size</span><span class="token punctuation">:</span> 320px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The first thing we should handle is to create a bit of basic styling for browsers that do not support this feature yet. Once again, we are using the following system for this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <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 atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>For the default styling, I want to set the pieces in play for our customized select as well. I think it’s rather nice to have a strong default. That way it becomes truly progressive enhancing instead of “graceful downgrading”. Here are the updated select styles and what we have so far. (info below the image)</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <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 property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> end<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 property">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">text-align-last</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token comment">/* Safari fix */</span> <span class="token property">border</span><span class="token punctuation">:</span> 4px dashed <span class="token function">var</span><span class="token punctuation">(</span>--color-space<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--orb-size<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">padding-block</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>--orb-size<span class="token punctuation">)</span> / 2<span class="token punctuation">)</span> 15px<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">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">&quot;Fondamento&quot;</span><span class="token punctuation">,</span> cursive<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 1px 1px #ccc<span class="token punctuation">,</span> inset 0 -1px 1px #aaa<span class="token punctuation">,</span> 0 2px 4px -3px #666<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 0.3s ease-out<span class="token punctuation">,</span> color 0.3s ease-out<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-lightest<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>--color-dark-liver<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token selector">&amp;:is(:hover, :focus)</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>--color-liver<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 30px 60px -12px inset<span class="token punctuation">,</span> <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.3<span class="token punctuation">)</span> 0px 18px 36px -18px inset<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">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/select-base.B0MeKSx5_206UBp.avif 320w, /_astro/select-base.B0MeKSx5_nil3p.avif 480w, /_astro/select-base.B0MeKSx5_Z1D5pS3.avif 800w, /_astro/select-base.B0MeKSx5_1gmEIc.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-base.B0MeKSx5_1bNM8r.webp 320w, /_astro/select-base.B0MeKSx5_ZpYMpy.webp 480w, /_astro/select-base.B0MeKSx5_Z2rnym1.webp 800w, /_astro/select-base.B0MeKSx5_s4wfe.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-base.B0MeKSx5_Z1qQEaG.png" srcset="/_astro/select-base.B0MeKSx5_ZBVBc8.png 320w, /_astro/select-base.B0MeKSx5_Z2eKbK8.png 480w, /_astro/select-base.B0MeKSx5_O3b7l.png 800w, /_astro/select-base.B0MeKSx5_Z1lFR5l.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>This is a great starting point for what we want to achieve. It’s a nice little orb with a fun hover state. So, important here is that we set the <code>width</code> with our custom property. I used a pixel value in the variables, but this can surely become a container unit, viewport unit, or whichever you desire. The idea I have is that we want this to work as a calculation throughout the code (you’ll see more of that soon). The font I’m using for this demo is <a href="https://fonts.google.com/specimen/Fondamento">Fondomento</a>, I thought it gave a rather nice touch to the theme. The rest of the code mostly contains padding, borders and an aspect ratio to keep things square (or round… in this case). I like to set <code>cursor: pointer</code> as well for selects, or is that just me?</p> <p>So… it does look alright, but it could be nice to give it a bit of extra “oomph” for browsers that don’t support this feature and add a little <code>background-image</code> for the flask. So in the example, the following was added:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* ...Previous code */</span> <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><span class="token string url">&quot;potion.svg&quot;</span><span class="token punctuation">)</span></span><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 property">background-position</span><span class="token punctuation">:</span> center 12px<span class="token punctuation">;</span> <span class="token property">background-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>--orb-size<span class="token punctuation">)</span> / 4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Hover state and support query... */</span> <span class="token punctuation">}</span> </code></pre> <p>I added a few background properties to position a flask in the center of the select. For demo purposes, I did not shorthand them in this article so that you can see what is going on more clearly, but in the final demo this became the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-dark-liver<span class="token punctuation">)</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">&quot;potion.svg&quot;</span><span class="token punctuation">)</span></span> center 12px / <span class="token function">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--orb-size<span class="token punctuation">)</span> / 4<span class="token punctuation">)</span> no-repeat<span class="token punctuation">;</span> </code></pre> <p>We now have our starting point:</p> <picture> <source srcset="/_astro/select-ff.xjDnkJoj_1oaKWL.avif 320w, /_astro/select-ff.xjDnkJoj_Z27DXnQ.avif 480w, /_astro/select-ff.xjDnkJoj_Z26obMU.avif 800w, /_astro/select-ff.xjDnkJoj_1EMw1l.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-ff.xjDnkJoj_1yLpaf.webp 320w, /_astro/select-ff.xjDnkJoj_Z1W3kbn.webp 480w, /_astro/select-ff.xjDnkJoj_Z1UMxAr.webp 800w, /_astro/select-ff.xjDnkJoj_1PoadO.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-ff.xjDnkJoj_2dT6AF.png" srcset="/_astro/select-ff.xjDnkJoj_Z17CQgx.png 320w, /_astro/select-ff.xjDnkJoj_qIxbL.png 480w, /_astro/select-ff.xjDnkJoj_rYjLH.png 800w, /_astro/select-ff.xjDnkJoj_ZQ16cX.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <picture> <source srcset="/_astro/select-ff-open.Crcc0K-n_1LM1Gn.avif 320w, /_astro/select-ff-open.Crcc0K-n_1LlPn5.avif 480w, /_astro/select-ff-open.Crcc0K-n_Z10d6ov.avif 800w, /_astro/select-ff-open.Crcc0K-n_2sHGJt.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-ff-open.Crcc0K-n_ZqlvYi.webp 320w, /_astro/select-ff-open.Crcc0K-n_ZqLHiA.webp 480w, /_astro/select-ff-open.Crcc0K-n_1QPtIK.webp 800w, /_astro/select-ff-open.Crcc0K-n_fz93N.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-ff-open.Crcc0K-n_28z7Bo.png" srcset="/_astro/select-ff-open.Crcc0K-n_Z1LNwk0.png 320w, /_astro/select-ff-open.Crcc0K-n_Z1MeHDi.png 480w, /_astro/select-ff-open.Crcc0K-n_vnto3.png 800w, /_astro/select-ff-open.Crcc0K-n_Z15RQgT.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h2 id="going-custom-anchoring-the-pickerselect-to-our-select-element">Going custom: anchoring the ::picker(select) to our select element</h2> <p>Everything is set in place for us to create a fun experience. We already had our opting-in set with the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>The way I see it, there are two ways to approach this, we could further CSS-nest everything inside of that select or we could start some new rules. I found the latter to be a bit cleaner, but this is of course subjective, and if you want to keep on nesting everything feel free to do so.</p> <p>So below the previous code, I added a new line with a feature query and started working inside of that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token property">padding-block</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token selector">&amp;::picker-icon</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">.icon</span> <span class="token punctuation">{</span> <span class="token property">width</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>--option-size<span class="token punctuation">)</span> * 0.375<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">calc</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--option-size<span class="token punctuation">)</span> * 0.625<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>20deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.15s<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>What happens here? We removed the padding of our select, as we don’t need it for the custom version, and also removed that <code>background-image</code> we set earlier as the <code>&lt;selectedcontent&gt;</code> will hold the actual potion from our checked option, so we don’t need that background anyway. We also added some styling for our SVG’s in there, giving them a <code>width</code> and <code>height</code> based on the previously set <code>--option-size</code> custom property.</p> <p><strong>note that for the rest of this article, I won’t be repeating that feature query, we will be working inside of it for the rest of the time</strong></p> <p>Below our select, let’s style our picker:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>180deg / 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><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>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> visible<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> overlay 0.5s<span class="token punctuation">,</span> display 0.5s<span class="token punctuation">;</span> <span class="token property">transition-behavior</span><span class="token punctuation">:</span> allow-discrete<span class="token punctuation">;</span> <span class="token comment">/* Removing some UA styles */</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">background</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 punctuation">}</span> </code></pre> <p>First thing you see here is that we added a new custom property <code>--rotation-divide</code>, this is used to place our options in a circle, this will set the default for the use-case when two options are available, more on that later. Let’s focus on the anchoring.</p> <p>The <code>::picker(select)</code> is inside the top-layer, located outside of the document flow. We don’t really need to provide an <code>anchor-name</code> for this because the picker is automatically associated with the select button via CSS anchor positioning. This way, we can directly use the anchor function and a little transform, making sure that the picker is set to the center.</p> <p>To remove potential flashing of scrollbars, the overflow here is set to visible and we are also adding a <code>transition-behavior</code> for top-layer transitions.</p> <h2 id="styling-the-radial-options">Styling the radial options</h2> <p>If you are actively following this tutorial, I suggest for the next steps, you reduce your select to only contain one option (just put the others in comment for now):</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>items<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>health<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>potion-holder<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>icon icon-health<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name"><span class="token namespace">xlink:</span>href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>#potion<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>Health<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Ready? Then it’s time to style our options, notice how we’re setting up some custom properties (<code>--half-circle</code>, <code>--deg</code>, <code>--negative</code>) that will be crucial for positioning and rotation later on.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token property">--half-circle</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>--circle-size<span class="token punctuation">)</span> / -2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--deg</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rotation-divide<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--negative</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>--deg<span class="token punctuation">)</span> / -1<span class="token punctuation">)</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">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 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> 50%<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--option-size<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">margin</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>--option-size<span class="token punctuation">)</span> / -2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-catawba<span class="token punctuation">)</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">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset 0 1px 1px #ccc<span class="token punctuation">,</span> inset 0 -1px 1px #aaa<span class="token punctuation">,</span> 0 2px 4px -3px #666<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.5s<span class="token punctuation">,</span> box-shadow 0.3s<span class="token punctuation">,</span> transform 0.5s<span class="token punctuation">;</span> <span class="token property">cursor</span><span class="token punctuation">:</span> grab<span class="token punctuation">;</span> <span class="token selector">&amp;::checkmark</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">&amp;:checked</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-catawba-light<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>The new variables: <code>--half-circle</code>, <code>--deg</code>, and <code>--negative</code> are being calculated based on other variables we’ve set before. <code>--half-circle</code> will help with positioning around the central orb, <code>--deg</code> will determine the rotation angle, and <code>--negative</code> will be its inverse. The reasoning behind this is that we set the option by default right over the main select orb by using absolute positioning and negative margins based on the options orb size. I’m sure there are some better ways to do this with trigonometric functions nowadays but to be honest, I am not that good at maths…</p> <p>We are also setting the options to an opacity of 0 because we want to fade them in using a transition. Further on, we will hide the default <code>::checkmark</code> of the customizable select, and set our own little background color for the select option using the <code>:checked</code> pseudo-class.</p> <p>Let’s also style our label inside of the option:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token comment">/* ... previous styles */</span> <span class="token selector">&amp; span</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">bottom</span><span class="token punctuation">:</span> -30px<span class="token punctuation">;</span> <span class="token property">left</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">translateX</span><span class="token punctuation">(</span>-50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.1rem<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-lightest<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">opacity</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>The text will also be absolutely positioned based on the option, and we’ll fade that in later on based on the <code>:open</code> state of the select, we can already add a little animation for that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> fade-in</span> <span class="token punctuation">{</span> <span class="token selector">from</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">to</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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>For now, we laid our groundwork, it’s time to make these options appear!</p> <h3 id="the-fan-out-effect">The fan-out effect</h3> <p>We have everything set for our transitions, and now we will make use of those custom properties to make those options “fan-out”</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select:open option</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--deg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">translate</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--half-circle<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">rotate</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--negative<span class="token punctuation">)</span><span class="token punctuation">)</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 selector">&amp; span</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> fade-in 0.4s ease-out forwards 0.4s<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Let me break down how this transform works:</p> <ul> <li><code>rotate(var(--deg))</code> rotates each option around the center of the select. The <code>--deg</code> variable will be calculated based on the number of options (we’ll add those in a bit).</li> <li><code>translate(var(--half-circle))</code> moves the rotated option outwards along its rotated axis, placing it on the circumference of an imaginary circle.</li> <li><code>rotate(var(--negative))</code> counter-rotates the option so that it remains upright despite being positioned on the circle.</li> </ul> <p>And to finalize things, let’s add a bit of a hover/focus effect that rotates the potion-icon a little bit and set a pronounced inset shadow as well. We’re also going to leave that potion rotated when the option is `:checked.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 30px 60px -12px inset<span class="token punctuation">,</span> <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.3<span class="token punctuation">)</span> 0px 18px 36px -18px inset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">option:is(:hover, :focus, :checked) .icon</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</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>This is what it currently looks like with one option:</p> <picture> <source srcset="/_astro/select-single.DZr-f9vR_1jMIwy.avif 320w, /_astro/select-single.DZr-f9vR_1RMA6K.avif 480w, /_astro/select-single.DZr-f9vR_ZWNCb7.avif 800w, /_astro/select-single.DZr-f9vR_630MG.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-single.DZr-f9vR_Z2lGNnT.webp 320w, /_astro/select-single.DZr-f9vR_Z1MGVNH.webp 480w, /_astro/select-single.DZr-f9vR_qRXHm.webp 800w, /_astro/select-single.DZr-f9vR_1uJBGa.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-single.DZr-f9vR_ZoxYqw.png" srcset="/_astro/select-single.DZr-f9vR_21Nv59.png 320w, /_astro/select-single.DZr-f9vR_Z2unL9A.png 480w, /_astro/select-single.DZr-f9vR_ZfMPCw.png 800w, /_astro/select-single.DZr-f9vR_N3Mlh.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But of course when adding a second option, the following will happen:</p> <picture> <source srcset="/_astro/select-bump.DkSR2UWO_gER5X.avif 320w, /_astro/select-bump.DkSR2UWO_Z1l8Hs2.avif 480w, /_astro/select-bump.DkSR2UWO_1HEEpr.avif 800w, /_astro/select-bump.DkSR2UWO_Zs4nMf.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/select-bump.DkSR2UWO_ZwCgn0.webp 320w, /_astro/select-bump.DkSR2UWO_Z29qPV0.webp 480w, /_astro/select-bump.DkSR2UWO_TmvVt.webp 800w, /_astro/select-bump.DkSR2UWO_Z1gmwgd.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/select-bump.DkSR2UWO_1TSq7N.png" srcset="/_astro/select-bump.DkSR2UWO_Z2lnEHz.png 320w, /_astro/select-bump.DkSR2UWO_16YSxm.png 480w, /_astro/select-bump.DkSR2UWO_ZTnRo6.png 800w, /_astro/select-bump.DkSR2UWO_204dd9.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>This is why these custom properties need to be updated based on the amount of children. For this, we’ll be using <code>:has()</code>, but once again. I am aware the easier calculations might be possible these days for those strong with trigonometric functions.</p> <h3 id="the-rotation-adjustment">The rotation adjustment</h3> <p>In the end this is what I want to achieve:</p> <picture> <source srcset="/_astro/potion-options.DIjwo-yT_voe6Y.avif 320w, /_astro/potion-options.DIjwo-yT_uX2MG.avif 480w, /_astro/potion-options.DIjwo-yT_Z2gASXT.avif 800w, /_astro/potion-options.DIjwo-yT_1cjTa5.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/potion-options.DIjwo-yT_Z1GJjyG.webp 320w, /_astro/potion-options.DIjwo-yT_Z1HauRY.webp 480w, /_astro/potion-options.DIjwo-yT_ArG9m.webp 800w, /_astro/potion-options.DIjwo-yT_Z10NDvA.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/potion-options.DIjwo-yT_2jIFFj.jpg" srcset="/_astro/potion-options.DIjwo-yT_Z1ADXg5.jpg 320w, /_astro/potion-options.DIjwo-yT_Z1B59zn.jpg 480w, /_astro/potion-options.DIjwo-yT_Gx2rX.jpg 800w, /_astro/potion-options.DIjwo-yT_ZTIicY.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>It’s a bit sad, that most people won’t see the full potential of this demo, but I absolutely love the way they were positioned based on the amount of items. O well….</p> <p>To achieve this behavior, there are two sets of rules:</p> <p>The first set uses the <code>:has()</code> pseudo-class in combination with <code>:nth-child()</code> to target the parent element <code>.items</code>, we can not target the <code>::picker(select)</code> directly with this method, which is why we needed that extra wrapper. Based on the number of options present, we’re recalculating the <code>--rotation-divide</code> custom property.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.items:has(option:nth-child(2))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.items:has(option:nth-child(3))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / 3<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.items:has(option:nth-child(4))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / 4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.items:has(option:nth-child(5))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / 5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.items:has(option:nth-child(6))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / 6<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Next, we have rules that target each specific option using <code>:nth-child()</code> and adjust their rotation:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option:nth-child(2)</span> <span class="token punctuation">{</span> <span class="token property">--deg</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>--rotation-divide<span class="token punctuation">)</span> * 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">option:nth-child(3)</span> <span class="token punctuation">{</span> <span class="token property">--deg</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>--rotation-divide<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">option:nth-child(4)</span> <span class="token punctuation">{</span> <span class="token property">--deg</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>--rotation-divide<span class="token punctuation">)</span> * 4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">option:nth-child(5)</span> <span class="token punctuation">{</span> <span class="token property">--deg</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>--rotation-divide<span class="token punctuation">)</span> * 5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">option:nth-child(6)</span> <span class="token punctuation">{</span> <span class="token property">--deg</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>--rotation-divide<span class="token punctuation">)</span> * 6<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>And the last rule here is that we only allow a certain amount of options, a maximum of 6 that is. This potion satchel isn’t infinite:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option:nth-child(1n + 7)</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> <h3 id="future-sibling-count-and-sibling-index">Future: <code>sibling-count()</code> and <code>sibling-index()</code></h3> <p>In the future, the previous calculations with <code>:has()</code> and <code>:nth-child()</code> could be reduced to a single line of code:</p> <p>At the place where we set our option styling:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <span class="token punctuation">{</span> <span class="token property">--half-circle</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>--circle-size<span class="token punctuation">)</span> / -2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--deg</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>--rotation-divide<span class="token punctuation">)</span> * <span class="token function">sibling-index</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Rest of the code */</span> <span class="token punctuation">}</span> </code></pre> <p>And the complete counter could be reduced by this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.items:has(option:nth-child(2))</span> <span class="token punctuation">{</span> <span class="token property">--rotation-divide</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span>360deg / <span class="token function">sibling-count</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> <p>If you’re feeling experimental, I added an <a href="https://codepen.io/utilitybend/pen/LEYvoNv" target="_blank">extra example for Chrome Canary that you can access here</a>.</p> <h2 id="the-final-result">The final result</h2> <p>I love the way we can use anchor positioning with the customizable select and I’ll be adding a few more of those examples later on. But this one might’ve gotten a bit complicated due to the rotation of the orbs. Sorry if I lost you there…</p> <p>I did however have a blast writing this article because this was one of my first select demos ever. It’s been through so many iterations of the customizable select and I’m happy that I could clean this up a little to write this article. It’s also been one of those demos that got share on quite a few interesting places such as the <a href="https://developer.chrome.com/blog/a-customizable-select">Chrome blog</a>. Does make me a feel proud though 🙂 I for one hope this demo may inspire you to create some other cool customizable selects.</p> <p>Here is that final demo!</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Potion selector" src="https://codepen.io/utilitybend/embed/preview/ZEPBGGR?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZEPBGGR"> Potion selector</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="other-articles-on-select">Other articles on Select:</h3> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu">The customizable select - Part five: Optgroup, creating a true select menu</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification">The customizable select - Part four: Scroll snapping, state queries, monster hunter, and gamification</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options">The customizable select - Part three: Sticky Options</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css">The customizable select - Part one: history, trickery, and styling the select with CSS</a></li> </ul>Brecht De RuyteInterop 2025 - Which browser features to get acquainted with this year and why you should carehttps://utilitybend.com/blog/interop-2025-which-browser-features-to-get-acquainted-with-this-year-and-why-you-should-care/https://utilitybend.com/blog/interop-2025-which-browser-features-to-get-acquainted-with-this-year-and-why-you-should-care/It’s been a few years since the Interop initiative was set in play. This has historically been one of the key elements in moving the web forward as it aims for a consistent web between browser vendors. In contrast to the “old days browser wars”, we as developers get streamlined features without having to check everything in every browser. The features of Interop 2025 are now known, features to be made stable across browsers by the end of the year. In this article, we’ll take a look at what we should get acquainted with in 2025.Fri, 28 Mar 2025 00:00:00 GMT<picture> <source srcset="/_astro/io.Dg3cHcwk_Z25b8NH.avif 375w, /_astro/io.Dg3cHcwk_tOkKt.avif 480w, /_astro/io.Dg3cHcwk_Z5wM08.avif 680w, /_astro/io.Dg3cHcwk_todc8.avif 800w, /_astro/io.Dg3cHcwk_ZAfldy.avif 980w, /_astro/io.Dg3cHcwk_ZmaGMW.avif 1024w, /_astro/io.Dg3cHcwk_20jHNz.avif 1660w, /_astro/io.Dg3cHcwk_1Wjfzb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/io.Dg3cHcwk_Z1Ab5it.webp 375w, /_astro/io.Dg3cHcwk_XOogH.webp 480w, /_astro/io.Dg3cHcwk_osgv6.webp 680w, /_astro/io.Dg3cHcwk_XogHm.webp 800w, /_astro/io.Dg3cHcwk_Z6fhHk.webp 980w, /_astro/io.Dg3cHcwk_ZmJlnD.webp 1024w, /_astro/io.Dg3cHcwk_1YK4dS.webp 1660w, /_astro/io.Dg3cHcwk_1EH8qP.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/io.Dg3cHcwk_VsBpc.jpg" srcset="/_astro/io.Dg3cHcwk_16WAjf.jpg 375w, /_astro/io.Dg3cHcwk_Z1oe3Uv.jpg 480w, /_astro/io.Dg3cHcwk_Z1XAbG7.jpg 680w, /_astro/io.Dg3cHcwk_Z1oEbtQ.jpg 800w, /_astro/io.Dg3cHcwk_Z2tiJTx.jpg 980w, /_astro/io.Dg3cHcwk_2bttFL.jpg 1024w, /_astro/io.Dg3cHcwk_ZwdevD.jpg 1660w, /_astro/io.Dg3cHcwk_Zirdpp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="iO tech_hub" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://techhub.iodigital.com/articles/interop-2025-which-browser-features-to-get-acquainted-with-this-year-and-why-you-should-care" target="_blank">Read the article at iO tech_hub</a></div>Brecht De RuyteThe customizable select - Part one: history, trickery, and styling the select with CSShttps://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css/https://utilitybend.com/blog/the-customizable-select-part-one-history-trickery-and-styling-the-select-with-css/The customizable select has entered Chrome 134, allowing us to style this form element to our hearts' content. I decided to write a series about this feature because there are so many cool things we can do with CSS these days to create some really unique and fun experiences. In this first article of the series, I want to talk a bit about the history, while also giving an in-depth guide on how you can start creating your first customizable select as a progressive enhancement, today.Thu, 13 Mar 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.CT19BSG6_22twgj.avif 375w, /_astro/visual.CT19BSG6_JQyX2.avif 480w, /_astro/visual.CT19BSG6_ZFa0lH.avif 680w, /_astro/visual.CT19BSG6_14KK25.avif 800w, /_astro/visual.CT19BSG6_1K4JF7.avif 980w, /_astro/visual.CT19BSG6_noJhs.avif 1024w, /_astro/visual.CT19BSG6_vX6hs.avif 1660w, /_astro/visual.CT19BSG6_Z1i9Jky.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.CT19BSG6_ZKv5Y4.webp 375w, /_astro/visual.CT19BSG6_Z2383hl.webp 480w, /_astro/visual.CT19BSG6_1B2vcQ.webp 680w, /_astro/visual.CT19BSG6_Z1IdRdi.webp 800w, /_astro/visual.CT19BSG6_Z12TRzg.webp 980w, /_astro/visual.CT19BSG6_Z41eBl.webp 1024w, /_astro/visual.CT19BSG6_4x7nE.webp 1660w, /_astro/visual.CT19BSG6_1efgl.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.CT19BSG6_Z246lWn.png" srcset="/_astro/visual.CT19BSG6_1Ye2Cu.png 375w, /_astro/visual.CT19BSG6_GB5kd.png 480w, /_astro/visual.CT19BSG6_ZIptYw.png 680w, /_astro/visual.CT19BSG6_11vgog.png 800w, /_astro/visual.CT19BSG6_1GOg2i.png 980w, /_astro/visual.CT19BSG6_Z1gsjqW.png 1024w, /_astro/visual.CT19BSG6_Z17SWqW.png 1660w, /_astro/visual.CT19BSG6_Z1H8bHU.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1081" class="img-fluid"> </picture> <p>This article could probably be read a 10-fold if I just named it: “10 ways to style your customizable select with CSS”. But I don’t want to do that. Instead I’m going to write a little series, starting with a bit of history and basic workings behind this new capability, and just follow it up with some in-depth examples and trickery.</p> <p>I do encourage you to read a bit about the history as it might be some good food for thought, but since this is quite the long article, I added a few anchors:</p> <ul> <li><a href="#the-history">The History</a></li> <li><a href="#styling-the-customizable-select">Styling the Customizable Select</a></li> <li><a href="#adding-images-in-the-select">Adding Images in the Select</a></li> <li><a href="#the-selectedcontent-element">The Selected Content Element</a></li> </ul> <h2 id="the-history">The history</h2> <p>The customizable select has been in the works for quite some time. Following the evolution of ideas on how we could solve the styling capabilities for developers was something I could witness close by. I’ve learned much about legacy, trade-offs and “naming things” by following along with <a href="https://open-ui.org/" target="_blank">Open UI</a> for a few years. Even if it was just giving my opinion during Open UI meetings, early adoption, and reporting bugs by creating demos for each iteration of “customizable select”, it puts a big smile on my face to see this feature land in Chrome as of version 134. The amount of work this costs is not to be underestimated, and so many people had a part in this. I, for one, am just really happy I could see it all take shape.</p> <p>More often than not when I give presentations about new UI capabilities, people ask me if I participate in these W3C community groups as a part of my job, or they wonder if I make some money doing it. The answer to that question is: “no, and I’m not the only one”. Same as in open source projects, some people do this kind of thing out of a passion. Maybe in some cases it’s about frustration as well? Eg: ”I’ve been doing this job for over 10 years, why can’t I style a select already?”</p> <h3 id="from-new---to-progressively-enhanced-element">From new - to progressively enhanced element</h3> <p>A lot had been done for this already, but the first iteration (end 2022) I witnessed and based demos on was a new element called the <code>&lt;selectmenu&gt;</code>. There was even a moment when I thought it would be: “the final version”! So, I did what I usually do: I <a href="https://www.smashingmagazine.com/2023/06/advanced-form-control-styling-selectmenu-anchoring-api/" target="_blank">created a fancy article/demo</a> and was convinced that this would be released in stable browsers (oh boy, was I wrong…). The months after, new names were bikeshedded and in about two months, there was a completely different name for this element: the <code>&lt;selectlist&gt;</code>. Ok, just a minor change, so I updated all my demos, feeling confident that this was just a little pre-release change for better naming, as a select isn’t a “menu”.</p> <p>Even after name changes and other adjustments, the proposals still lacked a bit of clarity, specifically about progressive enhancements and accessibility. A new element does make things a lot more complicated for accessibility: Keyboard controls need to be specified correctly, screen readers would need to be updated to recognize the new element, and users would have to update their screen reader software to even notice that new element for what it is. It could become a real hassle.</p> <p>One thing became clear during that period, there is a thin line on where you want to block authors for doing certain things. Do we give developers full control? Where should we block things? By default, everything should be accessible in HTML, let’s not forget that. It’s a thin line to which degree you want to give developers a pair of scissors and let them run with it. Please don’t take that the wrong way, I’m just as equally guilty in creating some shady stuff. But where and how could you avoid this, that’s the question.</p> <p>Let’s fast forward a little bit. In the end, what Open UI does, is write explainers. This kind of research goes on to be presented to the HTML and CSS working groups, and a lot of feedback can come from those steps. One thing that started to happen was collaborative meetings. Mostly due to bad hours for me to follow those, I only joined a few of them, to listen. But I did notice that a lot of handy feedback came from those meetings and things started to move forward at an inclined pace. Everyone loved the capability and the work by Open UI, the things we could do, but the new element idea, it just didn’t sit right. And that’s when a new idea was born…</p> <p><strong>Let’s re-use the select, and create an opt-in via CSS.</strong></p> <p>Can we? Shall we? This is where I got lost so many times, this is where talk started to happen on parsing HTML and what is and isn’t possible due to the browser engine and even OS differences. Because a select does differ based on OS as well, especially from an accessibility standpoint. Here is a party trick to do with your colleague:</p> <p>Create a select, “colleague one” views that select on Windows, the other one on macOS. Use your keyboard to access the select, focus on an option using arrow keys, press the <kbd>ESC</kbd> button, which colleague selected the focused option? Who still has the previous selected option? I’ll let you figure out the answer 😉</p> <p>Anyway, a lot of thought went to this progressive enhanced select, and I don’t know all the details on the browser engineering perspective. However, I did learn that implementing this kind of behavior was difficult. There is a huge <a href="https://github.com/whatwg/html/issues/9799" target="_blank">collection on GitHub by Joseph</a> that if you want to dive deep on the amount of work, I can highly recommend it.</p> <p>The most baffling thing is that someone noticed that when you put a <code>&lt;button&gt;</code> inside of a <code>&lt;select&gt;</code>, it just gets completely ignored by browsers and that became the window to truly progressively enhance the <code>&lt;select&gt;</code> and open up a variety of styling possibilities. <strong>Do not put an input element in there, for example</strong>. (I might get back on that one in a future article) It’s only the <code>&lt;button&gt;</code> element that gets ignored, you can not put an <code>&lt;input&gt;</code> element in there without breaking the backwards compatibility.</p> <p>Another benefit is that it would ignore unknown elements, which allowed the <code>&lt;selectedcontent&gt;</code> (first named: selectedoption) element to be nested inside of a <code>&lt;select&gt;</code>.</p> <p>I did spare out some details, conversations about keyboard navigation, the timing of cloning <code>&lt;selectedcontent&gt;</code>, naming (a lot of) things, pseudo-element choices and names, how the checkmark should be defined, etc.</p> <p>A lot is going on here, and that’s exactly why I at least wanted to write up a little bit of history. There are so many little choices, that we take for granted when new things get added to the web platform. People are excited for this, me being one of them. So many hours went into getting this to the platform. Every engineer, accessibility expert, developer, contributor, is doing a little part in moving this forward. I’ve at least learned to be a little less ignorant, by understanding why these things take time.</p> <p>All that time, sometimes frustratingly re-creating demos due to changed (pseudo-) element names or behavior - I believe the wait was certainly worth it.</p> <p>A lot of people will use the word “finally” when podcasting, writing, and video tutorialing the customizable select. But just as we forget with open source, there was a lot of work, for the better. So from me, to all those involved, whether you get paid to do these groups or not: thank you.</p> <p>Now let’s get styling!</p> <h2 id="styling-the-customizable-select">Styling the customizable select</h2> <p>Remember that this is a beginner tutorial so we’re going to keep things as easy as possible for now and create something like this:</p> <picture> <source srcset="/_astro/styled-select.aIEmsrrH_B572D.avif 320w, /_astro/styled-select.aIEmsrrH_1a4XBP.avif 480w, /_astro/styled-select.aIEmsrrH_Z1FweF2.avif 800w, /_astro/styled-select.aIEmsrrH_ZBEAGe.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-select.aIEmsrrH_20LHV7.webp 320w, /_astro/styled-select.aIEmsrrH_Z2vpyiC.webp 480w, /_astro/styled-select.aIEmsrrH_ZgOCLy.webp 800w, /_astro/styled-select.aIEmsrrH_M20cf.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-select.aIEmsrrH_Z17gAUr.png" srcset="/_astro/styled-select.aIEmsrrH_1j5SAe.png 320w, /_astro/styled-select.aIEmsrrH_1R5Kaq.png 480w, /_astro/styled-select.aIEmsrrH_ZXvs7r.png 800w, /_astro/styled-select.aIEmsrrH_5laQm.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with a listbox underneath it that is white and has a pink border" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="the-opt-in-via-css-and-the-anatomy-of-a-customizable-select">The opt-in via CSS and the anatomy of a customizable select</h3> <p>First thing we need to do is to create our <code>&lt;select&gt;</code> using HTML and do the opt-in via CSS, for this demo, I do want to progressively enhance this <code>&lt;select&gt;</code> a bit, because I believe this is where a lot of the power lies, so let’s start with some HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span><span class="token punctuation">&gt;</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">&quot;</span>first<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>First option<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>second<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Second option<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>third<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Third option<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now let’s opt-in via CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <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 atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>So, let’s explain this small CSS snippet a bit. First of all, I set my select to have <code>appearance: none;</code>, this was already possible, and I’m doing this because I do want a bit of styling for my select for browsers that do not support this new feature. Which brings me to the feature query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;, &amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This ensures that when the feature is supported, the select and the picker appear as <code>base-select</code>, opting into that customizable select capability.</p> <p>Let’s quickly compare side-by-side. On the left, you have Chrome 134, which supports this new behavior. On the right, you have another browser that falls back to the default <code>appearance: none</code>.</p> <picture> <source srcset="/_astro/side-by-side.RKmphDmR_Z2wxkjT.avif 320w, /_astro/side-by-side.RKmphDmR_19FHDM.avif 480w, /_astro/side-by-side.RKmphDmR_Z1w33kL.avif 800w, /_astro/side-by-side.RKmphDmR_Z1PXVth.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/side-by-side.RKmphDmR_Z29KTWd.webp 320w, /_astro/side-by-side.RKmphDmR_1ws81t.webp 480w, /_astro/side-by-side.RKmphDmR_Z19gCX5.webp 800w, /_astro/side-by-side.RKmphDmR_Z1tcw6A.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/side-by-side.RKmphDmR_ZigtKx.jpg" srcset="/_astro/side-by-side.RKmphDmR_1FzrsM.jpg 320w, /_astro/side-by-side.RKmphDmR_hBlCx.jpg 480w, /_astro/side-by-side.RKmphDmR_Z2o7pm1.jpg 800w, /_astro/side-by-side.RKmphDmR_2m8Pjp.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Two unstyled select next to each other, the left one had a listbox attached, the right one hasn't" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>But what happened when we opted in with CSS? Well, a lot of things became available to us, here is an image highlighting things we can target (explainer below):</p> <picture> <source srcset="/_astro/picker-select-info.Bv8l4Vbg_JJlx1.avif 320w, /_astro/picker-select-info.Bv8l4Vbg_kTGPP.avif 480w, /_astro/picker-select-info.Bv8l4Vbg_Z1kLDPK.avif 800w, /_astro/picker-select-info.Bv8l4Vbg_ZoVmuD.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/picker-select-info.Bv8l4Vbg_1SkObX.webp 320w, /_astro/picker-select-info.Bv8l4Vbg_1tvauM.webp 480w, /_astro/picker-select-info.Bv8l4Vbg_ZcbbbN.webp 800w, /_astro/picker-select-info.Bv8l4Vbg_IE69j.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/picker-select-info.Bv8l4Vbg_1nQEKS.jpg" srcset="/_astro/picker-select-info.Bv8l4Vbg_Z2910o2.jpg 320w, /_astro/picker-select-info.Bv8l4Vbg_2wltII.jpg 480w, /_astro/picker-select-info.Bv8l4Vbg_PE828.jpg 800w, /_astro/picker-select-info.Bv8l4Vbg_1Lupnf.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A select diagram showing the naming of the parts, picker-icon for the arrow on the right hand side, checkmark on the selected option as well as checked, :picker(select) for the box containing options" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>There are a few things that we can select with CSS directly:</p> <ul> <li><code>::picker-icon</code> : Allows us to change the little icon indicating the select (arrow-down)</li> <li><code>::checkmark</code> : allows us to alter the check icon that is next to the checked option</li> <li><code>:checked</code> : A pseudo-class to select our checked option</li> <li><code>::picker(select)</code> : The bounding box that surrounds/lists our options</li> </ul> <h3 id="styling-our-select">Styling our select</h3> <p>Let’s start with the basic look and feel of our select, we’re going to set <code>display: flex</code> to get the arrow positioned next to the text, give it a bit of <code>border-radius</code> and just some basic styling, it’s all good here, do something you like for this step:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</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 property">align-items</span><span class="token punctuation">:</span> center<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 property">border-radius</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid plum<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">font-weight</span><span class="token punctuation">:</span> 700<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 property">background-color</span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Next up, we’re going to make sure that we still have a good experience for browsers that don’t have the new support. Inside of my select, let’s add a little arrow by updating the background and paddings:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous properties, but updated the padding */</span> <span class="token property">padding-block</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">padding-inline</span><span class="token punctuation">:</span> 10px 30px<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> hotpink <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">&quot;arrow.svg&quot;</span><span class="token punctuation">)</span></span> right 10px center / 20px no-repeat<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This gives the padding at the end bit more room and provides a <code>background-image</code> of an arrow to clarify this is a select element. And there you have it, we have a perfectly styled select for browsers that don’t support this new feature:</p> <picture> <source srcset="/_astro/styled-ff._NePcrVQ_1fP1W3.avif 320w, /_astro/styled-ff._NePcrVQ_Z2fYHoz.avif 480w, /_astro/styled-ff._NePcrVQ_Z2eIUND.avif 800w, /_astro/styled-ff._NePcrVQ_1wrM0C.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-ff._NePcrVQ_1qqF9w.webp 320w, /_astro/styled-ff._NePcrVQ_Z25o4c6.webp 480w, /_astro/styled-ff._NePcrVQ_Z248hBa.webp 800w, /_astro/styled-ff._NePcrVQ_1H3qd6.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-ff._NePcrVQ_25ymzW.png" srcset="/_astro/styled-ff._NePcrVQ_Z1fXAhg.png 320w, /_astro/styled-ff._NePcrVQ_inNb3.png 480w, /_astro/styled-ff._NePcrVQ_jDzKY.png 800w, /_astro/styled-ff._NePcrVQ_ZYlPdG.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with white text and slightly rounded corners and an arrow" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <picture> <source srcset="/_astro/styled-open-ff.D3gUb-IH_FH2XC.avif 320w, /_astro/styled-open-ff.D3gUb-IH_FgQEk.avif 480w, /_astro/styled-open-ff.D3gUb-IH_Z26i57g.avif 800w, /_astro/styled-open-ff.D3gUb-IH_1mCI1I.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-open-ff.D3gUb-IH_Z1wquH3.webp 320w, /_astro/styled-open-ff.D3gUb-IH_Z1wQG1l.webp 480w, /_astro/styled-open-ff.D3gUb-IH_KKv10.webp 800w, /_astro/styled-open-ff.D3gUb-IH_ZPuODW.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-open-ff.D3gUb-IH_12u8SD.png" srcset="/_astro/styled-open-ff.D3gUb-IH_2ciCLb.png 320w, /_astro/styled-open-ff.D3gUb-IH_2bRrrS.png 480w, /_astro/styled-open-ff.D3gUb-IH_ZzGujH.png 800w, /_astro/styled-open-ff.D3gUb-IH_Z2bWOYE.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="An open select with options: first option, second option, third option" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="progressive-enhancing-our-customizable-select">Progressive enhancing our customizable select</h3> <p>Now that we have set a good basis, let’s dive a bit deeper. According to the anatomy, we can now access the <code>::picker-icon</code> pseudo-element to update our arrow, let’s do this right away.</p> <p>We will remove the <code>background-image</code> on our select itself and reset the <code>padding</code> in favor of using the pseudo-element as a progressive enhancement:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous properties with background-image */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">padding-inline</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">background-image</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token selector">&amp;::picker-icon</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 20px<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-image</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">&quot;arrow.svg&quot;</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> rotate 0.2s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Noticed that I added a transition on the <code>::picker-icon</code>? We also get an <code>:open</code> pseudo-class with this new feature, let’s put that in action and rotate our icon when the select is open:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token selector">&amp;::picker-icon</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:open::picker-icon</span> <span class="token punctuation">{</span> <span class="token property">rotate</span><span class="token punctuation">:</span> 180deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This is our current state when the select is open:</p> <picture> <source srcset="/_astro/styled-ch.BXNWdDS5_MTgbL.avif 320w, /_astro/styled-ch.BXNWdDS5_2mgEE5.avif 480w, /_astro/styled-ch.BXNWdDS5_2nwrf1.avif 800w, /_astro/styled-ch.BXNWdDS5_14w1fl.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-ch.BXNWdDS5_XuTof.webp 320w, /_astro/styled-ch.BXNWdDS5_2wRiQy.webp 480w, /_astro/styled-ch.BXNWdDS5_Z2w43mr.webp 800w, /_astro/styled-ch.BXNWdDS5_1f7ErO.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-ch.BXNWdDS5_1CCAOF.png" srcset="/_astro/styled-ch.BXNWdDS5_Z1HTm2x.png 320w, /_astro/styled-ch.BXNWdDS5_Z9wWze.png 480w, /_astro/styled-ch.BXNWdDS5_Z8haYi.png 800w, /_astro/styled-ch.BXNWdDS5_Z1rhAXX.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with white text and slightly rounded corners and an arrow. has a white list box under it" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="styling-the-select-picker-and-options">Styling the select picker and options</h3> <p>Now is the time to finally style what we came here for: styling the picker and options. With the <code>::picker(select)</code> pseudo-element we can style the box that contains our selects.</p> <p>For this part, let’s keep building it up inside of that <code>@supports</code> query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;:open::picker-icon</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::picker(select)</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 property">margin-top</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid hotpink<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 5px<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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This allowed us to style the listbox and this is the result:</p> <picture> <source srcset="/_astro/styled-without-options.D-AKep4X_1TCj6N.avif 320w, /_astro/styled-without-options.D-AKep4X_Z1n1jge.avif 480w, /_astro/styled-without-options.D-AKep4X_259rpv.avif 800w, /_astro/styled-without-options.D-AKep4X_Z5d17r.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-without-options.D-AKep4X_1wpQP1.webp 320w, /_astro/styled-without-options.D-AKep4X_Z1KdKx1.webp 480w, /_astro/styled-without-options.D-AKep4X_1GW08I.webp 800w, /_astro/styled-without-options.D-AKep4X_Zspsoe.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-without-options.D-AKep4X_C50Hp.png" srcset="/_astro/styled-without-options.D-AKep4X_Z1asQGe.png 320w, /_astro/styled-without-options.D-AKep4X_C4DJF.png 480w, /_astro/styled-without-options.D-AKep4X_ZYVInw.png 800w, /_astro/styled-without-options.D-AKep4X_1TRVSs.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with white text and slightly rounded corners and an arrow. has a styled list box under it with pink border and white background" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Wonderful! All that is left for us to do is to style the options themselves, in a nutshell, this is what I’ll be doing here:</p> <ul> <li>Remove the checked icon by setting the <code>::checkmark</code> pseudo-element to <code>display: none</code></li> <li>Create my own checked state with the <code>:checked</code> pseudo-class</li> <li>Add a bit of beautiful styling and some transitions</li> </ul> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</span> <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 property">border-top</span><span class="token punctuation">:</span> 1px solid plum<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">transition-property</span><span class="token punctuation">:</span> color<span class="token punctuation">,</span> background<span class="token punctuation">;</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.2s<span class="token punctuation">;</span> <span class="token property">transition-timing-function</span><span class="token punctuation">:</span> ease-out<span class="token punctuation">;</span> <span class="token selector">&amp;:where(:hover, :focus, :active)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> plum<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 selector">&amp;:checked</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> violet<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 selector">&amp;::checkmark</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">&amp;:first-child</span> <span class="token punctuation">{</span> <span class="token property">border</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>And there you have it, a beautifully styled select:</p> <picture> <source srcset="/_astro/styled-select.aIEmsrrH_B572D.avif 320w, /_astro/styled-select.aIEmsrrH_1a4XBP.avif 480w, /_astro/styled-select.aIEmsrrH_Z1FweF2.avif 800w, /_astro/styled-select.aIEmsrrH_ZBEAGe.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/styled-select.aIEmsrrH_20LHV7.webp 320w, /_astro/styled-select.aIEmsrrH_Z2vpyiC.webp 480w, /_astro/styled-select.aIEmsrrH_ZgOCLy.webp 800w, /_astro/styled-select.aIEmsrrH_M20cf.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/styled-select.aIEmsrrH_Z17gAUr.png" srcset="/_astro/styled-select.aIEmsrrH_1j5SAe.png 320w, /_astro/styled-select.aIEmsrrH_1R5Kaq.png 480w, /_astro/styled-select.aIEmsrrH_ZXvs7r.png 800w, /_astro/styled-select.aIEmsrrH_5laQm.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A pink select with white text and slightly rounded corners and an arrow. has a styled list box under it with pink border and white background and styled options without the checkmark" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="animations">Animations</h3> <p>I am going to keep the animation part for another articles as I’m planning to go a bit wild with animations for those. Do not forget that the picker is inside of the top-layer, so you will need to animate with that in mind. However, I don’t want to keep you hanging completely, so for this example you could do something such as animate the <code>height</code> and <code>opacity</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">select</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">appearance</span><span class="token punctuation">:</span> base-select<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">&amp;:open::picker-icon</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::picker(select)</span> <span class="token punctuation">{</span> <span class="token comment">/* ... */</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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">overflow</span><span class="token punctuation">:</span> clip<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> height 0.5s ease-out<span class="token punctuation">,</span> opacity 0.5s ease-out<span class="token punctuation">,</span> overlay 0.5s<span class="token punctuation">,</span> display 0.5s<span class="token punctuation">;</span> <span class="token property">transition-behavior</span><span class="token punctuation">:</span> allow-discrete<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:open::picker(select)</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">height</span><span class="token punctuation">:</span> <span class="token function">calc-size</span><span class="token punctuation">(</span>auto<span class="token punctuation">,</span> size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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 punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="final-note-on-the-first-example">Final note on the first example</h3> <p>In contrast to the non-customizable-select, the width of the select is fluid, meaning that it does not take the size of the largest option by default. So you will need to provide something for this. In the final example I added a <code>min-width</code>.</p> <p>Here is the full CodePen:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="A customizable select with only pseudo-elements" src="https://codepen.io/utilitybend/embed/preview/GgRrLWb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/GgRrLWb"> A customizable select with only pseudo-elements</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="adding-images-in-the-select">Adding images in the select’s options!</h2> <p>Now that we know the foundation of the customizable select, it might be interesting to know that this new feature allows us to add images or icons inside of the options. Re-using the same select (but with a bit of different styling), this is an example where I just added some SVGs inside of the options:</p> <p>You could do something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span><span class="token punctuation">&gt;</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">&quot;</span>pokeball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Pokeball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>greatball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Great ball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>ultraball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Ultra ball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And in my CSS, I updated my options a bit:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">option</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">align-items</span><span class="token punctuation">:</span> center<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 property">gap</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The result is that inside of my options the images are visible:</p> <picture> <source srcset="/_astro/pokeselect.DB1MKRES_ZPYzWN.avif 320w, /_astro/pokeselect.DB1MKRES_Zb9UG9.avif 480w, /_astro/pokeselect.DB1MKRES_Zxbo2I.avif 800w, /_astro/pokeselect.DB1MKRES_uypkB.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/pokeselect.DB1MKRES_ZxyNr0.webp 320w, /_astro/pokeselect.DB1MKRES_7fPOE.webp 480w, /_astro/pokeselect.DB1MKRES_ZeKBvU.webp 800w, /_astro/pokeselect.DB1MKRES_MYbQp.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/pokeselect.DB1MKRES_Z2wNXlf.png" srcset="/_astro/pokeselect.DB1MKRES_2kXhQu.png 320w, /_astro/pokeselect.DB1MKRES_Z24obFM.png 480w, /_astro/pokeselect.DB1MKRES_Z2qpE2m.png 800w, /_astro/pokeselect.DB1MKRES_Z1nEPE2.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A blue select on dark background, the select has the text pokeball on it, the dropdown has options visually showing images of a pokebal next to the text" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="A customizable select with images inside of the options" src="https://codepen.io/utilitybend/embed/preview/mydBgNx?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/mydBgNx"> A customizable select with images inside of the options</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>This is great, no harm done for non-supported browsers as they don’t get rendered for those, but wouldn’t it be nice if we were able to see that image inside of our select when it’s closed?</p> <h2 id="the-selectedcontent-element">The selectedcontent element</h2> <p>One of the great things is that buttons and new elements get ignored in browsers that don’t support this feature. Taking the same poké-adventure, let’s make that Pokéball pop up in the selected content. This is how I updated the HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>select</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>selectedcontent</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>selectedcontent</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>pokeball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://assets.codepen.io/159218/pokeball.svg<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Pokeball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>greatball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://assets.codepen.io/159218/great-ball.svg<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Great ball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>ultraball<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://assets.codepen.io/159218/ultra-ball.svg<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> Ultra ball <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>select</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The <code>&lt;selectedcontent&gt;</code> will clone the content of our selected option inside of it. We do need to style it a bit with CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">selectedcontent</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">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This is our final result:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="A customizable select with images inside of the options and the selectedcontent" src="https://codepen.io/utilitybend/embed/preview/ByawgNN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ByawgNN"> A customizable select with images inside of the options and the selectedcontent</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>In case you were wondering, if this feature is not supported, it is still a perfectly beautiful and usable select:</p> <picture> <source srcset="/_astro/pokeselect-ff.Dks18m5c_Z123QjU.avif 320w, /_astro/pokeselect-ff.Dks18m5c_Zt3YJI.avif 480w, /_astro/pokeselect-ff.Dks18m5c_1KvULl.avif 800w, /_astro/pokeselect-ff.Dks18m5c_Z2fNz3M.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/pokeselect-ff.Dks18m5c_mCJyy.webp 320w, /_astro/pokeselect-ff.Dks18m5c_UCB8K.webp 480w, /_astro/pokeselect-ff.Dks18m5c_Z1TXB97.webp 800w, /_astro/pokeselect-ff.Dks18m5c_ZQ6Xaj.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/pokeselect-ff.Dks18m5c_2jLyvV.png" srcset="/_astro/pokeselect-ff.Dks18m5c_Zk34Lk.png 320w, /_astro/pokeselect-ff.Dks18m5c_dVLMR.png 480w, /_astro/pokeselect-ff.Dks18m5c_2swHjV.png 800w, /_astro/pokeselect-ff.Dks18m5c_Z1xMMvc.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A blue select on dark background, the select has the text pokeball on it" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h3 id="one-more-step">One more step</h3> <p>The last thing I want to address is that we can visually hide some text inside of that <code>&lt;selectedcontent&gt;</code>, thus really manipulate the look and feel between the select itself and the options. I am not going to go in detail on this right now, but this means you could do something like this:</p> <picture> <source srcset="/_astro/manipulated-selected-content.DX30RjJO_1ymtIv.avif 320w, /_astro/manipulated-selected-content.DX30RjJO_1hKepo.avif 480w, /_astro/manipulated-selected-content.DX30RjJO_XA5PN.avif 800w, /_astro/manipulated-selected-content.DX30RjJO_2hRVhG.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/manipulated-selected-content.DX30RjJO_Zc4Yy3.webp 320w, /_astro/manipulated-selected-content.DX30RjJO_ZsGeRa.webp 480w, /_astro/manipulated-selected-content.DX30RjJO_ZLQnqK.webp 800w, /_astro/manipulated-selected-content.DX30RjJO_wqs08.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/manipulated-selected-content.DX30RjJO_E3tPR.png" srcset="/_astro/manipulated-selected-content.DX30RjJO_1iLUK2.png 320w, /_astro/manipulated-selected-content.DX30RjJO_12aFqU.png 480w, /_astro/manipulated-selected-content.DX30RjJO_I0wRk.png 800w, /_astro/manipulated-selected-content.DX30RjJO_22injd.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A blue select on dark background, the select has the text pokeball on it" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>More on that later, but <a href="https://codepen.io/utilitybend/pen/WbNZBWQ" target="_blank">if you can’t wait to dig in, you can find that CodePen here</a>.</p> <h2 id="much-more-to-come">Much more to come</h2> <p>In the next few articles, I’m going to focus on some specific demos and techniques to create some really cool gamified experiences. I’m really happy this feature landed in Chrome and I just love playing around with it. It gives the ability to create some fun experiences, never forget to have some fun with the web.</p> <p>What’s more, <a href="https://cssday.nl/">I will be talking about this in depth at CSS Day this year</a>!</p> <p>I know this was a long article, I promise the next ones will be a bit shorter. But here is the foundation for you to start creating those awesome customizable selects as a progressive enhancement!</p> <p>Happy selecting!</p> <h3 id="other-articles-on-select">Other articles on Select:</h3> <ul> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-two-potions-anchoring-and-radial-shenanigans-in-css">The customizable select - Part two: Potions, anchoring, and radial shenanigans in CSS</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-three-sticky-options">The customizable select - Part three: Sticky Options</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-four-scroll-snapping-state-queries-monster-hunter-and-gamification">The customizable select - Part four: Scroll snapping, state queries, monster hunter, and gamification</a></li> <li><a href="https://utilitybend.com/blog/the-customizable-select-part-five-optgroup-creating-a-true-select-menu">The customizable select - Part five: Optgroup, creating a true select menu</a></li> </ul>Brecht De RuyteLove at first slide! Creating a carousel purely out of CSShttps://utilitybend.com/blog/love-at-first-slide-creating-a-carousel-purely-out-of-css/https://utilitybend.com/blog/love-at-first-slide-creating-a-carousel-purely-out-of-css/It’s Valentine's Day! As usual, I want to write about something I love, and this time it’s still highly experimental but something I am extremely excited about. Something new is on the horizon named CSS scroll-markers and it allows you to create a fully functional slider without the need of any JavaScript. Don’t believe me? Let’s take a look, shall we? Here is my first impression of creating a carousel with only CSS and some accessibility concerns as well.Fri, 14 Feb 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual-carousel.OSuiBwCJ_Z12CYv.avif 375w, /_astro/visual-carousel.OSuiBwCJ_220Uq0.avif 480w, /_astro/visual-carousel.OSuiBwCJ_92gjH.avif 680w, /_astro/visual-carousel.OSuiBwCJ_B6HcY.avif 800w, /_astro/visual-carousel.OSuiBwCJ_RtsO9.avif 980w, /_astro/visual-carousel.OSuiBwCJ_ZplHfa.avif 1024w, /_astro/visual-carousel.OSuiBwCJ_2feDfx.avif 1660w, /_astro/visual-carousel.OSuiBwCJ_ZSoIpO.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-carousel.OSuiBwCJ_2uoghJ.webp 375w, /_astro/visual-carousel.OSuiBwCJ_ZwJj6G.webp 480w, /_astro/visual-carousel.OSuiBwCJ_Z2pHXcY.webp 680w, /_astro/visual-carousel.OSuiBwCJ_Z1WDwjH.webp 800w, /_astro/visual-carousel.OSuiBwCJ_Z1GgKHx.webp 980w, /_astro/visual-carousel.OSuiBwCJ_1hkder.webp 1024w, /_astro/visual-carousel.OSuiBwCJ_Z18gz4M.webp 1660w, /_astro/visual-carousel.OSuiBwCJ_VTwqD.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-carousel.OSuiBwCJ_ekfc7.jpg" srcset="/_astro/visual-carousel.OSuiBwCJ_uTVUB.jpg 375w, /_astro/visual-carousel.OSuiBwCJ_Z2wdCsO.jpg 480w, /_astro/visual-carousel.OSuiBwCJ_EYQeO.jpg 680w, /_astro/visual-carousel.OSuiBwCJ_184i86.jpg 800w, /_astro/visual-carousel.OSuiBwCJ_1or3Jg.jpg 980w, /_astro/visual-carousel.OSuiBwCJ_yO6NA.jpg 1024w, /_astro/visual-carousel.OSuiBwCJ_Z1PLFuD.jpg 1660w, /_astro/visual-carousel.OSuiBwCJ_ZO69q3.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="3840" height="2160" class="img-fluid"> </picture><p>Carousels, who likes them? Clients! Yes, clients love them. Developers, not that much… During my career, I’ve spent a lot of time finding the perfect slider, tweaking the default styles of slider libraries, and hacking them to be awesome. This was usually accompanied by a lot of swearing behind my keyboard. But maybe - just maybe - this new idea of creating a carousel with CSS might be a way to remove a lot of that frustration and, dare I say, even make creating sliders something enjoyable.</p> <p>Let’s start with some basic theory before I show you what it can do.</p> <h2 id="scroll-markers-what-are-they">Scroll markers what are they?</h2> <p>As usual, it starts with a new magical CSS property: <a href="https://www.w3.org/TR/css-overflow-5/#scroll-markers" target="_blank" rel="noreferrer noopener">scroll-marker-group</a>.</p> <p>When this new property is set to a <a href="https://www.w3.org/TR/css-overflow-3/#scroll-container" target="_blank" rel="noreferrer noopener">scroll container</a> (aka, a box with scrolling overflow) It creates a new <code>::scroll-marker-group</code> pseudo-element attached to that container.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.some-container</span> <span class="token punctuation">{</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">scroll-marker-group</span><span class="token punctuation">:</span> after<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This <code>::scroll-marker-group</code> has three possible values:</p> <ul> <li>none</li> <li>before</li> <li>after</li> </ul> <p>Compared to the <code>::before</code> and <code>::after</code> pseudo-elements, this value will place this new <code>::scroll-marker-group</code> as a sibling preceding or following the scroll container it is attached to.</p> <p>But that <code>::scroll-marker-group</code> does not do anything by itself, it is a container that collects different scroll-markers, more about those later.</p> <h2 id="the-new-scroll-button-pseudo-element">The new ::scroll-button() pseudo-element</h2> <p>When you add a <code>::scroll-button(&lt;direction&gt;)</code> pseudo-element on your scroll container, you can add some buttons that move the scroll position based on the value of their flow-relative direction:</p> <ul> <li><code>::scroll-button(inline-start)</code></li> <li><code>::scroll-button(inline-end)</code></li> <li><code>::scroll-button(block-start)</code></li> <li><code>::scroll-button(block-end)</code></li> </ul> <p>To make them visible, you will need to add the <code>content</code> property, just as you would with a <code>::before</code>, <code>::after</code> pseudo-element.</p> <p>According to the spec, they are both focusable and activatable by default, with their activation behavior being to scroll their originating element by one “page” in their associated direction, similar to pressing the <kbd>PgUp</kbd>/<kbd>PgDn</kbd> keys, But we’ll get to that later.</p> <h2 id="the-new-scroll-marker-pseudo-element">The new ::scroll-marker pseudo-element</h2> <p>Just like our <code>::before</code> and <code>::after</code> pseudo-elements, all elements will be able to have a <code>::scroll-marker</code> pseudo-element (yes, all of them). Just as our previously known pseudo-elements, we’ll need to give this a <code>content</code> property in order to work. These new elements have some special knowledge:</p> <ul> <li>They contain the scroll-target of the element they’re attached to</li> <li>They behave like anchor links when you click on them.</li> <li>They know when their corresponding element is visible in the scroll container</li> <li>They have a stylable pseudo-class when their linked element is visible: <code>:target-current</code></li> </ul> <p>Ok, I tried to keep this short and to the point, there are probably a lot of caveats here, but let’s get into the sliding of things.</p> <h2 id="snaps-with-benefits">Snaps with benefits</h2> <p>The groundwork for creating a slider with CSS was already done some time ago. We already can use <a href="https://web.dev/articles/css-scroll-snap" target="_blank" rel="noreferrer noopener">scroll-snapping</a> in our projects, but it always felt like it missed a bit of control.</p> <p>Without further ado, let’s create a basic slider with just CSS. First, let’s set up the HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>carousel<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And let’s make this a scroll-snapping container, notice that I created it so that the images have an aspect ratio of 16/9, it’s a simple technique here, but that’s the whole point, keep the first demo as simple as possible:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</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-columns</span><span class="token punctuation">:</span> 100%<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">overflow-x</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">overscroll-behavior-x</span><span class="token punctuation">:</span> contain<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<span class="token punctuation">;</span> <span class="token property">scrollbar-width</span><span class="token punctuation">:</span> none<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 selector">div</span> <span class="token punctuation">{</span> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">scroll-snap-stop</span><span class="token punctuation">:</span> always<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">img</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">aspect-ratio</span><span class="token punctuation">:</span> 16/9<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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Ok, great, we now have our scroll-snapping container in place. A little note on this: You can perfectly use <code>proximity</code> in your <code>scroll-snap-type</code> property as well, however for this example to give that real slider feel on scroll, <code>mandatory</code> works best from a UX standpoint. We are also hiding the scrollbar with <code>scrollbar-width: none;</code> because we are going to provide our custom controls. We also set the <code>scroll-behavior: smooth;</code> as this will give a nice feel when the controls are in place.</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A container horizontal overflowing scroll snapping."><source src="/_astro/basic-scroll-snap.B2z5pzuw.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>Ok, this is our basic set up, let’s add the markers:</p> <h2 id="creating-the-controls-slider-bullets">Creating the controls: slider bullets</h2> <p>Let’s just dive in right away and add the following to our previous code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code */</span> <span class="token property">scroll-marker-group</span><span class="token punctuation">:</span> after<span class="token punctuation">;</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This will open that group of markers, we can clearly see this <code>::scroll-marker-group</code> in our DevTools, which is a very nice feature to have:</p> <picture> <source srcset="/_astro/devtools-scroll-marker-group.D2a9HOXH_Z27BGDK.avif 320w, /_astro/devtools-scroll-marker-group.D2a9HOXH_RWc5P.avif 480w, /_astro/devtools-scroll-marker-group.D2a9HOXH_ZSKH9O.avif 800w, /_astro/devtools-scroll-marker-group.D2a9HOXH_2dUYA5.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-scroll-marker-group.D2a9HOXH_r18b2.webp 320w, /_astro/devtools-scroll-marker-group.D2a9HOXH_Z1CB6Sj.webp 480w, /_astro/devtools-scroll-marker-group.D2a9HOXH_1ER7EX.webp 800w, /_astro/devtools-scroll-marker-group.D2a9HOXH_ZhCjo4.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-scroll-marker-group.D2a9HOXH_Z16lsPn.png" srcset="/_astro/devtools-scroll-marker-group.D2a9HOXH_Y04VN.png 320w, /_astro/devtools-scroll-marker-group.D2a9HOXH_Z15Ca7x.png 480w, /_astro/devtools-scroll-marker-group.D2a9HOXH_2cQ4qJ.png 800w, /_astro/devtools-scroll-marker-group.D2a9HOXH_flCmH.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="psuedo-classes of the scroll-marker-group in chrome devtools" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Ok, moving on, it’s time to add those <code>::scroll-marker</code> pseudo-elements to our sliders children:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code*/</span> <span class="token property">scroll-marker-group</span><span class="token punctuation">:</span> after<span class="token punctuation">;</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code*/</span> <span class="token selector">&amp;::scroll-marker</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">content</span><span class="token punctuation">:</span> <span class="token string">&quot; &quot;</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">width</span><span class="token punctuation">:</span> 24px<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">border</span><span class="token punctuation">:</span> 1px solid silver<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">background</span><span class="token punctuation">:</span> transparent<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 property">transition</span><span class="token punctuation">:</span> background 0.5s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Let’s update this just a little further before I show you what we created. In the theory, I mentioned that we can set a style when a <code>::scroll-marker</code> is active, let’s do that as well and give the little bullet a background when that happens:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code*/</span> <span class="token property">scroll-marker-group</span><span class="token punctuation">:</span> after<span class="token punctuation">;</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code*/</span> <span class="token selector">&amp;::scroll-marker</span> <span class="token punctuation">{</span> <span class="token comment">/* scroll marker base styles */</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::scroll-marker:where(:hover, :active, :focus)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> lightgray<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::scroll-marker:target-current</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This is what we can see now in our DevTools:</p> <picture> <source srcset="/_astro/devtools-scroll-markers.ReEEN24U_IuvHj.avif 320w, /_astro/devtools-scroll-markers.ReEEN24U_Z1ypymB.avif 480w, /_astro/devtools-scroll-markers.ReEEN24U_Z22avWT.avif 800w, /_astro/devtools-scroll-markers.ReEEN24U_Z2aChox.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-scroll-markers.ReEEN24U_ZSq3bd.webp 320w, /_astro/devtools-scroll-markers.ReEEN24U_1SQ0xN.webp 480w, /_astro/devtools-scroll-markers.ReEEN24U_1q62Wv.webp 800w, /_astro/devtools-scroll-markers.ReEEN24U_1hDhvR.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-scroll-markers.ReEEN24U_ZxbOc8.png" srcset="/_astro/devtools-scroll-markers.ReEEN24U_1K8Ll3.png 320w, /_astro/devtools-scroll-markers.ReEEN24U_ZwLiIR.png 480w, /_astro/devtools-scroll-markers.ReEEN24U_Z10wgka.png 800w, /_astro/devtools-scroll-markers.ReEEN24U_Z18Y1KN.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="psuedo-classes of the scroll markers in chrome devtools" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Now they are just sitting there on the side of my slider, which isn’t ideal, however, it does work very nicely:</p> <video playsInline="" width="320" height="240" controls="" preload="metadata" aria-label="The scrollmarkers are at the left bottom of the slider, bad placed but functional, they slide the container"><source src="/_astro/scroll-marker-wrong-position.BvJgwYuq.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>This is where our <code>::scroll-marker-group</code> comes into play, it’s quite refreshing to see something like this in CSS as it dedicates itself as a wrapper around the children’s <code>::scroll-marker</code> pseudo-element, meaning that this wrapper can be used for layout as well:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token comment">/* Previous code */</span> <span class="token selector">&amp;::scroll-marker-group</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">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 12px<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">padding</span><span class="token punctuation">:</span> 20px 0<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 property">z-index</span><span class="token punctuation">:</span> 20<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* children styling */</span> <span class="token punctuation">}</span> </code></pre> <p>And there you have it, a fully functional slider:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="The scrollmarkers are at the center bottom of the slider, they slide the container"><source src="/_astro/scroll-markers-done.i2H6fqxO.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="adding-the-scroll-buttons">Adding the scroll buttons</h2> <p>Let’s add a left and right arrow button for our slider. The <code>::scroll-button()</code> pseudo-element accepts logical naming, so let’s add this right away:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::scroll-button(inline-end), &amp;::scroll-button(inline-start)</span> <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">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token property">inline-size</span><span class="token punctuation">:</span> 44px<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">background-color</span><span class="token punctuation">:</span> <span class="token function">rgb</span><span class="token punctuation">(</span>255 255 255 / .6<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"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">&quot;arrow-left.svg&quot;</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::scroll-button(inline-end)</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <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><span class="token string url">&quot;arrow-right.svg&quot;</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::scroll-button(inline-end):where(:hover, :focus, :active), &amp;::scroll-button(inline-start):where(:hover, :focus, :active)</span> <span class="token punctuation">{</span> <span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">rgb</span><span class="token punctuation">(</span>255 255 255<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/devtools-scroll-button.Ci6znUOR_15wRQ6.avif 320w, /_astro/devtools-scroll-button.Ci6znUOR_1R5xjy.avif 480w, /_astro/devtools-scroll-button.Ci6znUOR_1PQNAf.avif 800w, /_astro/devtools-scroll-button.Ci6znUOR_Znutwk.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-scroll-button.Ci6znUOR_1oN0pg.webp 320w, /_astro/devtools-scroll-button.Ci6znUOR_2blERI.webp 480w, /_astro/devtools-scroll-button.Ci6znUOR_2a7V9p.webp 800w, /_astro/devtools-scroll-button.Ci6znUOR_Z4elXa.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-scroll-button.Ci6znUOR_1h1qhC.png" srcset="/_astro/devtools-scroll-button.Ci6znUOR_Z1WDivB.png 320w, /_astro/devtools-scroll-button.Ci6znUOR_Z1b5D39.png 480w, /_astro/devtools-scroll-button.Ci6znUOR_Z1cjmLs.png 800w, /_astro/devtools-scroll-button.Ci6znUOR_1DvsTT.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="psuedo-classes of the scrollbutton in chrome devtools" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>I kept the properties to a minimum here, but of course, they are a bit more styled in the final version (you can watch the full code in the demo later on). Let’s break down this code a bit:</p> <ul> <li>First, I set the scroll buttons to have an empty <code>content</code> property.</li> <li>As I want them to have arrows, I also give them a <code>background-image</code> containing an arrow</li> <li>Lastly, I set hover and active states for these buttons to light up a little bit.</li> </ul> <p>All that is left for us, is to position these buttons. For my use case, I found the quickest way was to use anchor positioning in CSS. This is how I positioned them:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --carousel<span class="token punctuation">;</span> <span class="token selector">&amp;::scroll-button(inline-end), &amp;::scroll-button(inline-start)</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 property">cursor</span><span class="token punctuation">:</span> pointer<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">position-anchor</span><span class="token punctuation">:</span> --carousel<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inset-inline</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>start<span class="token punctuation">)</span> <span class="token function">anchor</span><span class="token punctuation">(</span>end<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;::scroll-button(inline-end)</span> <span class="token punctuation">{</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inset-inline</span><span class="token punctuation">:</span> auto <span class="token function">anchor</span><span class="token punctuation">(</span>end<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>By setting an <code>anchor-name</code> on my scroll container (<code>.carousel</code>), it’s really easy to position the pseudo-elements on it. I can do this by using the <code>anchor()</code> function on my scroll buttons to position them right into place. As for some readers, this anchor positioning might still feel like a new thing. I wrote a little <a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">guide to anchor positioning in CSS</a> before.</p> <p>If you read this article this far, I have good news! You made it!, This is the final result and the CodePen. I tested this experimental feature in Chrome Canary. If you want to play around with them you’ll have to enable the <code>Experimental Web Platform features</code> after going to <code>chrome://flags</code></p> <video width="320" height="240" controls="" preload="metadata" aria-label="The scrollmarkers are at the center bottom of the slider, they slide the container"><source src="/_astro/base-slider-done.CHGTTBUZ.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Carousel Pure HTML and CSS" src="https://codepen.io/utilitybend/embed/preview/vEBQxNb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/vEBQxNb"> Carousel Pure HTML and CSS</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p><strong>Note:</strong> In the CodePen I used layers to make it easier to follow along with this tutorial.</p> <h2 id="further-experimentation-and-cool-stuff-on-scroll-markers-in-css">Further experimentation (and cool stuff on scroll-markers in CSS)</h2> <p>I created a bunch of fun stuff with this… It’s kinda how this goes for me, when I love a feature, I can’t help myself. I might do an in-depth tutorial of one later on…</p> <h3 id="experiment-1-inherit-colors-in-scroll-marker-and-full-axis-slider">Experiment 1: Inherit colors in ::scroll-marker and full-axis slider</h3> <p>The fun thing about the <code>::scroll-marker</code> pseudo-element is that they are children of our carousel’s children. Which in turn means we can <code>inherit</code> colors: <br> <br> For example:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.slider-item:nth-child(1)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>0.73 0.28 339.69<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">oklch</span><span class="token punctuation">(</span>0.92 0.22 101.77<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.slider-item:nth-child(2)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>0.92 0.22 101.77<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">oklch</span><span class="token punctuation">(</span>0.73 0.28 339.69<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.slider-item::scroll-marker</span> <span class="token punctuation">{</span> <span class="token property">border-color</span><span class="token punctuation">:</span> currentColor<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I combined this with a scroll-snap over both axis to create a 3by3 slider. And this my first Valentines Day Demo:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A slider scrolling horizontally verticlal a 3by3 grid using markers, only one item is visible at the time"><source src="/_astro/valentine-demo.BKd6aBhg.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll Markers 3by3 grid carousel" src="https://codepen.io/utilitybend/embed/preview/WbeqKNv?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/WbeqKNv"> Scroll Markers 3by3 grid carousel</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="experiment-2-recreating-a-slick-slider-with-just-css">Experiment 2: Recreating a slick-slider with just CSS</h3> <p>This one might deserve a new article on itself later on, but recently, Chrome release the new <code>attr()</code> possibilities. Which allows reading more attributes from HTML into our CSS. <a href="https://una.im/advanced-attr/" target="_blank">Read about the new attribute capabilities here</a>.</p> <p>In my years as a developer, I created a lot of webshops, so inspiration for this was easily found</p> <p>This is the experiment:</p> <p>In my HTML I did the following:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>carousel<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">data-src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>url(my-slick-image.webp)<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>my-slick-image.webp<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- other items in the same way --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>I added a <code>data-src</code> attribute on my sliding item, with the new update mentioned above, this means I can read this inside of my <code>::scroll-marker</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel &gt; div</span> <span class="token punctuation">{</span> <span class="token comment">/* Get the attribute of the div containing the background-image (Chrome since v133) */</span> <span class="token property">--image</span><span class="token punctuation">:</span> <span class="token function">attr</span><span class="token punctuation">(</span>data-src <span class="token function">type</span><span class="token punctuation">(</span>&lt;image&gt;<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token selector">&amp;::scroll-marker</span> <span class="token punctuation">{</span> <span class="token comment">/* Set image variable from parent as background */</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--image<span class="token punctuation">,</span> green<span class="token punctuation">)</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-repeat</span><span class="token punctuation">:</span> no-repeat<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Result:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A slider with thumbnails underneath it, displaying the main image as a marker"><source src="/_astro/slick-slider.DJk_YFqa.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Webshop slick slider remake in CSS" src="https://codepen.io/utilitybend/embed/preview/bNbXZWb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/bNbXZWb"> Webshop slick slider remake in CSS</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="experiment-3-more-than-one-item-on-a-slide">Experiment 3: More than one item on a slide</h3> <p>As a last one, I wanted to show the capability on skipping some items. In this example, I wanted my slider items to be shown 3 by 3 and snapped in the center. Now because of this, I wanted my slider items to scroll by 3 using scroll-markers, and this is how I did that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.carousel &gt; *:nth-child(3n + 2)::scroll-marker</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</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">width</span><span class="token punctuation">:</span> 24px<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">border</span><span class="token punctuation">:</span> 1px solid silver<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">background</span><span class="token punctuation">:</span> transparent<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 property">transition</span><span class="token punctuation">:</span> background 0.5s ease-out<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <video width="320" height="240" controls="" preload="metadata" aria-label="A slider scrolling three items at the same time using markers"><source src="/_astro/three-items.C1SkA_y8.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Carousel scroll-markers - scroll by three" src="https://codepen.io/utilitybend/embed/preview/qEBWgwq?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/qEBWgwq"> Carousel scroll-markers - scroll by three</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="accessibility-concerns---love-is-in-the-air">Accessibility concerns - Love is in the air</h2> <p>While I was experimenting, I noticed that when focusing on the buttons and markers, we do not get a default focus ring, do take note that this is still a work in progress and it might be a bug, or a feature yet to be developed. Also, the current scroll markers don’t show up in the accessibility tree, but I’ve been informed that this is due to a bug and it’s been reported.</p> <p>Since we’re talking about accessibility, maybe a quick hot tip for the future: If your slider contains a lot of items, try to be so kind as to provide a <a href="https://css-tricks.com/how-to-create-a-skip-to-content-link/" target="_blank" rel="noreferrer noopener">skip-link</a> at the start of it, your users will thank you for it.</p> <h2 id="learn-more-from-adam">Learn more from Adam</h2> <p>If you think these demos are crazy, wait until you see what <a href="https://nerdy.dev/" target="_blank" rel="noreferrer noopener">Adam</a> from the Chrome DevRel team made! <a href="https://chrome.dev/carousel/" target="_blank" rel="noreferrer noopener">Adam is working on a full Carousel Gallery also combining state queries and a lot of other goodies, check it out!</a></p> <p>He will also be talking a bit about this on <a href="https://cssday.nl/" target="_blank" rel="noreferrer noopener">CSS Day</a>, I’ll be there to bombard him with questions on it for sure, and if you can’t make it, no worries, I’m sure he and the team will be writing some in-depth about this future.</p> <p><strong>Other resources</strong></p> <ul> <li><a href="https://shoptalkshow.com/659/">Shop Talk Show</a></li> <li><a href="https://chrome.dev/carousel-configurator/">Carousel configurator</a></li> </ul> <p>I can’t wait to experiment more with this. I still have some question marks on it, but this might be one of those features I never thought we’d get in CSS. It can reduce dependencies, I can style it just the way I want without overruling some baked-in library styles… You know what… I’m gonna say it:</p> <p><strong>I love sliders!</strong> <br> Happy Valentines Day</p>Brecht De RuyteTransitioning Top-Layer Entries And The Display Property In CSShttps://utilitybend.com/blog/transitioning-top-layer-entries-display-property-css/https://utilitybend.com/blog/transitioning-top-layer-entries-display-property-css/It’s not always the big features that make our everyday lives easier; sometimes, it’s those ease-of-life features that truly enhance our projects. In this article, I want to highlight two such features: @starting-style and transition-behavior — two properties that are absolutely welcome additions to your everyday work with CSS animations.Wed, 29 Jan 2025 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2025/01/transitioning-top-layer-entries-display-property-css/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteRevisiting SVG filters - my forgotten powerhouse for duotones, noise, and other effectshttps://utilitybend.com/blog/revisiting-svg-filters-my-forgotten-powerhouse-for-duotones-noise-and-other-effects/https://utilitybend.com/blog/revisiting-svg-filters-my-forgotten-powerhouse-for-duotones-noise-and-other-effects/The year was 2018, and we were still trying to cope with IE11 and had to find some ways for certain effects. Things that we take for granted with CSS nowadays were not possible and one of the solutions to create awesome effects on the web was using SVG Filters. Fast forward to 2025, SVG filters are still awesome, but I use them less. In this article, I wanted to revisit them, with some easy-to-use effects we can use today.Mon, 27 Jan 2025 00:00:00 GMT<picture> <source srcset="/_astro/visual.BtXUO9qs_1LJTrK.avif 375w, /_astro/visual.BtXUO9qs_u7W9t.avif 480w, /_astro/visual.BtXUO9qs_ZUSCag.avif 680w, /_astro/visual.BtXUO9qs_O28dw.avif 800w, /_astro/visual.BtXUO9qs_Z5Jhd.avif 980w, /_astro/visual.BtXUO9qs_1Qg6Dz.avif 1024w, /_astro/visual.BtXUO9qs_1YOsDz.avif 1660w, /_astro/visual.BtXUO9qs_Ryk72.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.BtXUO9qs_Z11eHMC.webp 375w, /_astro/visual.BtXUO9qs_Z2iQF5T.webp 480w, /_astro/visual.BtXUO9qs_1liSoi.webp 680w, /_astro/visual.BtXUO9qs_Z1XWu1Q.webp 800w, /_astro/visual.BtXUO9qs_2h6Lhl.webp 980w, /_astro/visual.BtXUO9qs_1oP7JL.webp 1024w, /_astro/visual.BtXUO9qs_1xotJL.webp 1660w, /_astro/visual.BtXUO9qs_2bWjHV.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.BtXUO9qs_2wyFGd.jpg" srcset="/_astro/visual.BtXUO9qs_Z2dBgmk.jpg 375w, /_astro/visual.BtXUO9qs_1yWU9k.jpg 480w, /_astro/visual.BtXUO9qs_8VkOA.jpg 680w, /_astro/visual.BtXUO9qs_1SR6dn.jpg 800w, /_astro/visual.BtXUO9qs_14JdHD.jpg 980w, /_astro/visual.BtXUO9qs_ZhdU9x.jpg 1024w, /_astro/visual.BtXUO9qs_Z8Ey9x.jpg 1660w, /_astro/visual.BtXUO9qs_EShMC.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><svg viewBox="0 0 0 0" width="0" height="0" aria-hidden="true"><filter id="grayscale"><feColorMatrix type="matrix" values=" .33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"></feColorMatrix></filter><filter id="redstogreens"><feColorMatrix type="matrix" values=" 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0"></feColorMatrix></filter><filter id="darkergrayscale"><feColorMatrix type="matrix" values=" 0.5 0.2 0.8 0 0 0.5 0.2 0.8 0 0 0.5 0.2 0.8 0 0 0 0 0 1 0"></feColorMatrix></filter><filter id="neopan"><feColorMatrix type="matrix" values=" 0.5 0.44 0.1 0 -.02 0.5 0.44 0.1 0 -.02 0.5 0.44 0.1 0 -.02 0 0 0 1 0"></feColorMatrix><feComponentTransfer><feFuncR type="linear" slope="1" intercept="-0.02"></feFuncR><feFuncG type="linear" slope="1" intercept="-0.02"></feFuncG><feFuncB type="linear" slope="1" intercept="-0.02"></feFuncB></feComponentTransfer></filter><filter id="inverted"><feColorMatrix type="matrix" values=" .33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 1 1 1 0 0"></feColorMatrix></filter><filter id="duotone-purple"><feColorMatrix type="matrix" values=".33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"></feColorMatrix><feComponentTransfer color-interpolation-filters="sRGB"><feFuncR type="table" tableValues="0.878431373 0.788235294"></feFuncR><feFuncG type="table" tableValues="0.141176471 0.784313725"></feFuncG><feFuncB type="table" tableValues="0.435294118 0.17254902"></feFuncB></feComponentTransfer></filter></svg> <p>Before you get angry and yell at me that SVGs are still relevant, let me tell you that I’m completely aware of this. I see a lot of great people creating amazing things with them, but the truth is that in my day job, I don’t use them that often. It’s kind of a shame and recently I had a project where I found a good use case for them once again, so I thought to myself: “maybe I should revisit some of those basic effects?” This means that I will be keeping things at a basic level. Back in 2018, I didn’t write on this blog, and I used to be quite good at writing those filters by hand, so here is me documenting some of my favorite effects I used back then as I noticed some of those skills got lost over time. By no means am I an expert when it comes to SVG filters, which is exactly why I wanted to write about them.</p> <h2 id="creating-an-svg-filter">Creating an SVG filter</h2> <p>In this article, I’ll be focussing on SVG filters that we can call in our CSS. To achieve this, you’ll need an SVG element with a filter defined inside of it. This filter will have a unique ID that we can target through our CSS <code>filter</code> property.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-filter<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- filter effects --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.some-thing</span> <span class="token punctuation">{</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>#my-filter<span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="duotones-and-other-color-manipulations">Duotones and other color manipulations</h2> <p>Who doesn’t love a good old duotone? To illustrate this, I will be using the same image over and over again. It’s a picture taken the first time I went to Berlin.</p> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Before we get into the SVGs, let’s take a quick look at the built-in filters we have available in CSS.</p> <p>We can mimic duotones in CSS, for example, the filter property does have a <code>grayscale()</code> filter, so we can turn our image into black and white. We also have a <code>sepia()</code> duotone ready-to-use:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token function">sepia</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>By combining sepia and a <code>hue-rotate()</code> filter we can even sort of mimic a colored duotone with CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token function">sepia</span><span class="token punctuation">(</span>100%<span class="token punctuation">)</span> <span class="token function">hue-rotate</span><span class="token punctuation">(</span>250deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: sepia(100%) hue-rotate(250deg);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Even though this looks kinda great, it still lacks a bit of control, especially If we want to work with brand colors for our clients. This is where SVG filters come in handy.</p> <h3 id="color-svg-filter-part-1-black-and-white">Color SVG filter part 1: Black and White</h3> <p>Before we go into the coloring part, the first thing we’ll need to do is set our image into black and white. But just this one action already opens up a lot of opportunities.</p> <p>To manipulate color, the best way to start is with the <code>&lt;feColorMatrix&gt;</code>. This is a real powerhouse and I’ll try to explain the basics as easy as possible. Let’s start by recreating a grayscale:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0 0 0 0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grayscale<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span> .33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The “fe” stands for “Filter Effects”, this is something we’ll see a lot in the rest of this article. So we added the ColorMatrix effect with a bunch of values. The syntax looks a bit overwhelming (with all those values), but trust me, these values do make a lot of sense, and we’ll get there, just a quick example of our result when targeting with CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>#grayscale<span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: url(#grayscale);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Let’s dive into those values so that you can play around with them. I on purposely set those values in 4 rows. And this is what they do:</p> <pre class="language-plaintext" data-language="plaintext"><code is:raw="" class="language-plaintext">[ R ] : [ r g b a o ] [ G ] : [ r g b a o ] [ B ] : [ r g b a o ] [ A ] : [ a a a a o ] </code></pre> <p>Each of the first three rows specifies which color we want to adjust. For example, the first row will let us change the reds, the first three values inside of it are where we declare the new color for all our reds, followed by our alpha transparency and offset.</p> <p>As an overly simplified example. If I wanted all my reds to turn into greens, all my greens into reds, and keep the blues it would look like this:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span> 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: url(#redstogreens);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>So by setting each of those channels to <code>.33</code> you create a grayscale, just as you would in an RGB color <code>rgb(10 10 10)</code>. You want each column to have the same value to create a grayscale image.</p> <p>This already opens a lot more possibilities compared to our CSS grayscale filter as we can adjust the darkness based on each channel. In this example. I want my blues really light and my reds somewhere in the middle, creating a completely different type of grayscale:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span> 0.5 0.2 0.8 0 0 0.5 0.2 0.8 0 0 0.5 0.2 0.8 0 0 0 0 0 1 0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: url(#darkergrayscale);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>A few things I didn’t mention yet is that the last row of the matrix <code>[A]</code> affects the alpha (transparency) channel. The last column of the matrix provides additive offsets to the color channel, I sometimes use this to darken or lighten my grayscale as well. That opens a whole bunch of other possibilities that I haven’t invested enough time in (yet), but feel free to play around with it to notice its effects.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span> .33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 1 1 1 0 0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: with a duotone purple filter on it" style="filter: url(#inverted);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <h4 id="extra-creating-a-neopan-like-black-and-white">Extra: Creating a NEOPAN-like black and white</h4> <p>As I said before, there are endless possibilities with SVG filters. When I shoot in black &amp; white with my camera, I always try to mimic a Fuji NEOPAN style of film. I just love the contrasts and the dramatic colors. You could emulate these kinds of things. It’s not perfect for every use-case but this is an exaggeration of it:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0 0 0 0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>neopan<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span> 0.5 0.44 0.1 0 -.02 0.5 0.44 0.1 0 -.02 0.5 0.44 0.1 0 -.02 0 0 0 1 0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncR</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>linear<span class="token punctuation">&quot;</span></span> <span class="token attr-name">slope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span> <span class="token attr-name">intercept</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-0.02<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncG</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>linear<span class="token punctuation">&quot;</span></span> <span class="token attr-name">slope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span> <span class="token attr-name">intercept</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-0.02<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncB</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>linear<span class="token punctuation">&quot;</span></span> <span class="token attr-name">slope</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span> <span class="token attr-name">intercept</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-0.02<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: url(#neopan);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>As you see, I added an extra filter named <code>&lt;feComponentTransfer&gt;</code>. I’ll keep the explanation of this example short:</p> <p><code>&lt;feFuncR&gt;</code>, <code>&lt;feFuncG&gt;</code>, and <code>&lt;feFuncB&gt;</code> perform a color component transfer function. This is handy to adjust contrast and brightness after the color transformation we did with <code>&lt;feColorMatrix&gt;</code>.</p> <p>In short, for each of those RGB color functions we can change the following:</p> <ul> <li> <p><strong>Slope</strong>: Adjusts the contrast for that channel. <code>&gt; 1</code> increase contrast, <code>&lt; 1</code> lowers the contrast</p> </li> <li> <p><strong>Intercept</strong>: Brightens the image when <code>&gt; 1</code>, darkens the image when <code>&lt; 1</code></p> </li> </ul> <p>Note that I’m only going about the “linear” value for the type attribute, there is a lot more to it and the options can vary based on that type. (for reference, the other are: identity | table | discrete | linear | gamma). All these SVG filters probably deserve an article on their own. However, this particular one will be re-used for our duotone.</p> <p>So now that we’ve gotten this far in all the black &amp; white settings. The next part should be a lot easier:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="NEOPAN-ish" src="https://codepen.io/utilitybend/embed/preview/OPLzbXR?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/OPLzbXR"> NEOPAN-ish</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="color-svg-filter-part-2-duotone">Color SVG filter part 2: Duotone!</h3> <p>Let’s start by creating our cool duotone colors. First thing we’ll need to do to achieve this effect is to set our image in grayscale once again. If you were following along, that should be rather easy by now.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0 0 0 0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>duotone-purple<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>.33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feColorMatrix</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This creates a nice and simple grayscale image to start with as we did before:</p> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: 3 hi-rise buildings in Berlin" style="filter: url(#grayscale);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Alright, so we’ve got our grayscale image, and now we want to inject some color, turning it into a proper duotone. We’re going to use the <code>&lt;feComponentTransfer&gt;</code> element again, just like in the NEOPAN example, but this time with a twist. This filter effect lets us manipulate the color channels individually after the grayscale conversion.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feComponentTransfer</span> <span class="token attr-name">color-interpolation-filters</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>sRGB<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncR</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>.996078431 .984313725<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncR</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncG</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>.125490196 .941176471<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncG</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncB</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>.552941176 .478431373<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncB</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Remember those <code>&lt;feFuncR&gt;</code>, <code>&lt;feFuncG&gt;</code>, and <code>&lt;feFuncB&gt;</code> elements? They’re back, and this time we’ll be using the <code>type=&quot;table&quot;</code> attribute. This is where the magic happens for creating duotone colors.</p> <p>Instead of linear which we used before to adjust contrast and brightness, table lets us create a lookup table. What this table does, is that for each input value, we provide a corresponding output value.</p> <p>The <code>tableValues</code> attribute is the key here. It’s a space-separated list of numbers between <code>0</code> and <code>1</code>. In this case, we’re providing two values for each color channel. This means we are defining two points on a graph. The filter interpolates between these points.</p> <p>But how do we get these colors? It’s easiest explained with an example. Let’s set our duotone in the following two colors:</p> <p><code>rgb(224 36 111)</code> and <code>rgb(201 200 44)</code></p> <p>As I mentioned before, we are not using RGB color values but a value between <code>0</code> and <code>1</code>. For each of our color channels, we are going to get that value by getting the channel value and divide it by <code>255</code>. Here is the rundown:</p> <p><code>rgb(224 36 111)</code>:</p> <ul> <li>Red: 224 / 255 = 0.878431373</li> <li>Green: 36 / 255 = 0.141176471</li> <li>Blue: 111 / 255 = 0.435294118</li> </ul> <p><code>rgb(201 200 44)</code>:</p> <ul> <li>Red: 201 / 255 = 0.788235294</li> <li>Green: 200 / 255 = 0.784313725</li> <li>Blue: 44 / 255 = 0.17254902</li> </ul> <p>We will follow up by adding those values in this manner:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feComponentTransfer</span> <span class="token attr-name">color-interpolation-filters</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>sRGB<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncR</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>RED1 RED2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncR</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncG</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>GREEN1 GREEN2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncG</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncB</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>BLUE1 BLUE2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncB</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>So our final result will be the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feComponentTransfer</span> <span class="token attr-name">color-interpolation-filters</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>sRGB<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncR</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.878431373 0.788235294<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncR</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncG</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.141176471 0.784313725<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncG</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncB</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.435294118 0.17254902<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncB</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Here is our full code:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0 0 0 0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>duotone-purple<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feColorMatrix</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>matrix<span class="token punctuation">&quot;</span></span> <span class="token attr-name">values</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>.33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feColorMatrix</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feComponentTransfer</span> <span class="token attr-name">color-interpolation-filters</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>sRGB<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncR</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.878431373 0.788235294<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncR</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncG</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.141176471 0.784313725<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncG</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feFuncB</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>table<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tableValues</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.435294118 0.17254902<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feFuncB</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feComponentTransfer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <picture> <source srcset="/_astro/default-image.DaH9myU5_Z1JaGCE.avif 320w, /_astro/default-image.DaH9myU5_Z1baP3s.avif 480w, /_astro/default-image.DaH9myU5_13p5sB.avif 800w, /_astro/default-image.DaH9myU5_27gIrp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/default-image.DaH9myU5_Zkt5Jb.webp 320w, /_astro/default-image.DaH9myU5_dvKP1.webp 480w, /_astro/default-image.DaH9myU5_2s6Gm5.webp 800w, /_astro/default-image.DaH9myU5_Z1ydNt3.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/default-image.DaH9myU5_1BEIdc.png" srcset="/_astro/default-image.DaH9myU5_Z129U54.png 320w, /_astro/default-image.DaH9myU5_Zta3uR.png 480w, /_astro/default-image.DaH9myU5_1KpR1c.png 800w, /_astro/default-image.DaH9myU5_Z2fTCNV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The base image: with a duotone purple filter on it" style="filter: url(#duotone-purple);" loading="lazy" decoding="async" fetchpriority="auto" width="200" height="133"> </picture> <p>Here is that codepen:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Duotone purple-ish" src="https://codepen.io/utilitybend/embed/preview/OPLzREP?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/OPLzREP"> Duotone purple-ish</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Here is an example with more filters:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Barnaby duotones" src="https://codepen.io/utilitybend/embed/preview/qEWQqgN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/qEWQqgN"> Barnaby duotones</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p><strong>Fun fact: This article actually just contains that one image with all the effects, cool huh?</strong></p> <h2 id="motion-blur">Motion blur</h2> <p>Alright, so we’ve played with colors and duotones. Now, let’s take a look into something a bit different: blur. Blur can be used for a variety of effects, from subtle background softening to creating a sense of depth or movement. Let me tell you: SVG blur has a lot more control than the CSS <code>blur()</code> filter.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.background</span> <span class="token punctuation">{</span> <span class="token property">filter</span><span class="token punctuation">:</span> <span class="token function">blur</span><span class="token punctuation">(</span>5px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The blur filter unfortunately only accepts one value, while in SVG filters you can set an <code>X</code> and <code>Y</code> value in which you want a blur to happen. Here is an example of that filter:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>0 0 500 350<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>blurX<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feGaussianBlur</span> <span class="token attr-name">stdDeviation</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>15 0<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>feGaussianBlur</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Notice the <code>stdDeviation=&quot;15 0&quot;</code> on our <code>GaussianBlur</code> filter effect:</p> <p>The first value (15) controls the blur radius in the x-axis, while the second value (0) sets the vertical or y–axis blur to 0.</p> <p>There isn’t that much more to say about this effect, but I did create a demo to illustrate the difference between the two types of blur:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="SVG motion blur vs CSS blur demo" src="https://codepen.io/utilitybend/embed/preview/raBrpdb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/raBrpdb"> SVG motion blur vs CSS blur demo</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>At the end of the article, I have a little something that has the vertical blur as well, stay tuned ;)</p> <h3 id="extra-info-on-the-svg-horizontal-blur-demo">Extra info on the SVG horizontal blur demo</h3> <p>I created a bunch of filters in this demo and targeted the background, road, and car.</p> <p>With a tiny bit of JS, I made those filters change their value, each and every one of those has a different maximum, which I calculated.</p> <p>I’m also using a CSS variable that sets the animation speed and am using <code>:has()</code> to toggle between the radio buttons (because I’m lazy, do avoid many <code>:has()</code> checks on the root though, as it could cause performance issues, but for this demo, it’s fine…)</p> <p>I don’t mind giving a complete rundown of this demo if people would be interested. Just give a little shout-out. But to keep this article from getting too bloated, let’s quickly move on.</p> <h2 id="noise">Noise</h2> <p><span style="text-transform: uppercase;">Let’s hear some noise for svg!!!</span><br>Ok… dad jokes… sorry.</p> <p>Noise is one of the things that can be created with <code>&lt;feTurbulence&gt;</code>, but it’s just the tip of the iceberg as this filter effect can create so much… From subtle textures to dramatic effects. To create a noise filter, you can use something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>0 0 500 350<span class="token punctuation">&quot;</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">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>filter</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>noiseFilter<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>feTurbulence</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fractalNoise<span class="token punctuation">&quot;</span></span> <span class="token attr-name">baseFrequency</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0.85<span class="token punctuation">&quot;</span></span> <span class="token attr-name">numOctaves</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>3<span class="token punctuation">&quot;</span></span> <span class="token attr-name">stitchTiles</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>stitch<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>filter</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>There are a few attributes inside of this turbulence filter, here is the short explanation of them:</p> <ul> <li><code>type=&quot;fractalNoise&quot;</code>: specifies the type of noise to generate. fractalNoise creates a more natural, organic-looking noise.</li> <li><code>baseFrequency=&quot;0.85&quot;</code>: is the frequency of the noise. Higher values result in smaller, more detailed noise, while lower values create larger, more spread-out noise. Play around with this value to create the desired effect</li> <li><code>numOctaves=&quot;3&quot;</code>: determines the number of octaves (dare I say layers?) of noise to combine. More octaves create a more detailed and complex noise pattern</li> <li><code>stitchTiles=&quot;stitch&quot;</code>: this attempts to create seamless transitions between the tiles of noise, creating a beautiful repeating pattern</li> </ul> <p>Since I recently started to brush up my drawing skills with a pencil, I created a hamster with CSS based on a drawing and added some noise to it:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="hamtaro" src="https://codepen.io/utilitybend/embed/preview/NPKBoRE?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/NPKBoRE"> hamtaro</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>This is pretty much all you need to know when it comes to noise, but there are SVG wizards that create amazing stuff with turbulence. And I am far off an SVG wizard. Still, a bit of noise does come in handy from time to time.</p> <h2 id="much-more">Much more…</h2> <p>There are a lot more things we can do with SVG. I brushed up an old demo I created five years ago (that was kept on a USB stick… can you imagine?) to show some of the other effects you could create with SVG. But as I stated before, I wanted to keep this article introductory and I think my current knowledge just isn’t enough to give an accurate description on how to use displacement maps or <code>&lt;feComposite&gt;</code> which combines filter effects into one, as the examples below:</p> <iframe height="300" style="width: 100%;" scrolling="no" title="Grungy letters with SVG filters" src="https://codepen.io/utilitybend/embed/preview/ZYzjmVr?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZYzjmVr"> Grungy letters with SVG filters</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe> <p>I also spotted this cool <a href="https://codepen.io/collection/DPYwYN" target="_blank" rel="noreferrer noopener">collection of SVG text effects on CodePen</a>.</p> <p>If you want to learn more about displacement maps check this Smashing Magazine article: <a href="https://www.smashingmagazine.com/2021/09/deep-dive-wonderful-world-svg-displacement-filtering/" target="_blank" rel="noreferrer noopener">A Deep Dive Into The Wonderful World Of SVG Displacement Filtering</a></p> <h2 id="in-conclusion">In conclusion…</h2> <p>So, I wrote this article not as a person who knows all things SVG but as someone who suddenly remembered that in this cool era of new and awesome CSS capabilities, we still have room for some solid SVG tinkering. I don’t use SVG filters on a day-to-day basis, but it’s a shame to lose some of those skills while they can still be very relevant today. It probably depends on the kind of work you do. But hey, this is just a reminder that when you stumble upon a certain effect that would be cool for a client and you have no idea how to achieve it, think SVG, it might just be the solution.</p> <p>Happy tinkering!</p> <h2 id="bonus-hamster-parachute">Bonus: hamster parachute</h2> <p>I was creating my demo with the car and thought about doing something with a parachute for vertical motion blur. Then our 6-year-old daughter came into the room and showed her best project management skills. Besides criticism on some of my other demo here were the key asks:</p> <ul> <li>Can you make the parachute go up as well?</li> <li>Can you hang something on the parachute?</li> </ul> <p>And so this happened:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Parachute hamster!" src="https://codepen.io/utilitybend/embed/preview/EaYpGyr?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/EaYpGyr"> Parachute hamster!</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div>Brecht De RuyteReflecting on a year well spent! Looking forward to 2025https://utilitybend.com/blog/reflecting-on-a-year-well-spent-looking-forward-to-2025/https://utilitybend.com/blog/reflecting-on-a-year-well-spent-looking-forward-to-2025/Who publishes an article on the last day of the year? This guy! I felt like I should honor the tradition of at least recapping the past year by writing one final article in 2024. So this time, there will be no tutorials or hidden code gems. I already created my holiday CSS greeting card. This is just a little personal reflection and future plans for 2025.Tue, 31 Dec 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-2025._dCQdqn3_nCAGc.avif 375w, /_astro/visual-2025._dCQdqn3_dztlD.avif 480w, /_astro/visual-2025._dCQdqn3_1FwlVw.avif 680w, /_astro/visual-2025._dCQdqn3_1WgDoj.avif 800w, /_astro/visual-2025._dCQdqn3_Z2aDlay.avif 980w, /_astro/visual-2025._dCQdqn3_ZRxxr3.avif 1024w, /_astro/visual-2025._dCQdqn3_Z24wXtD.avif 1660w, /_astro/visual-2025._dCQdqn3_Z2e3fNF.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-2025._dCQdqn3_ZpEwLL.webp 375w, /_astro/visual-2025._dCQdqn3_ZzHE7k.webp 480w, /_astro/visual-2025._dCQdqn3_Redsy.webp 680w, /_astro/visual-2025._dCQdqn3_18XuUl.webp 800w, /_astro/visual-2025._dCQdqn3_26fEap.webp 980w, /_astro/visual-2025._dCQdqn3_ZuL84m.webp 1024w, /_astro/visual-2025._dCQdqn3_Z1GKy6W.webp 1660w, /_astro/visual-2025._dCQdqn3_ZOlDUc.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-2025._dCQdqn3_ZQuz61.jpg" srcset="/_astro/visual-2025._dCQdqn3_1TnAX.jpg 375w, /_astro/visual-2025._dCQdqn3_Z88IIA.jpg 480w, /_astro/visual-2025._dCQdqn3_1jN8Qi.jpg 680w, /_astro/visual-2025._dCQdqn3_1Axqj5.jpg 800w, /_astro/visual-2025._dCQdqn3_Z2wmyfM.jpg 980w, /_astro/visual-2025._dCQdqn3_Z1JBTsi.jpg 1024w, /_astro/visual-2025._dCQdqn3_28zNj3.jpg 1660w, /_astro/visual-2025._dCQdqn3_1iUpsj.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="text 2025 with some fireworks behind it" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="a-record-in-writing-articles-this-year">A record in writing articles this year</h2> <p>This year, I will have written 22 articles, and taking into account that I want to write a minimum of one article/month means that I’m way above target. This is something I try to think about going into next year. I might not achieve that number in 2025, especially combined with giving more presentations and some other goals (more on those later), I want to be realistic about that.</p> <p>Articles were spread on my blog, <a href="https://techhub.iodigital.com/authors/brecht-de-ruyte" target="_blank" rel="noreferrer noopener">iO tech_hub</a>, and <a href="https://www.smashingmagazine.com/author/brecht-de-ruyte/" target="_blank" rel="noreferrer noopener">Smashing Magazine</a>, and they were mentioned in many newsletters by so many people I look up to… I am a complete social (media) awkward person from time to time, still, it does mean a lot to me. All the shares, the likes. It’s not my prior reason for writing, which is still mostly about documenting and pushing myself to learn more and help others. But the love does help me to go the extra mile: The extra demo, some screenshots, some video, etc…</p> <p>I had articles <a href="https://coliss.com/articles/build-websites/operation/css/relative-length-units-based-on-font.html" target="_blank" rel="noreferrer noopener">translated into Japanese</a>, which was a fun thing to happen for sure. I went to Japan a decade ago and want to go back someday… (just dropping a hint here)</p> <h3 id="next-year-articles">Next year: articles.</h3> <p>As stated before, I’ll keep it realistic, one article each month is still the idea. We’ll see if I write more. I hope so. I still didn’t use any ads or sponsored posts and will keep it that way. Some might have noticed that I added a “buy me a coffee” button. I don’t expect much of it, but 😀you’re always welcome to.</p> <h2 id="presenting-am-i-a-public-speaker-now">Presenting. Am I a “public speaker” now?</h2> <p>If you’d told me a year ago If I would speak at such a variety of conferences, I probably would’ve laughed. It’s been great… I’m not going to be falsely modest and I do realize that I must be doing some things right. I handle speaking with the same care as articles, making sure it’s something that people can enjoy, giving it a personal tone while at the same time trying to be as informative as possible.</p> <p>From Middlesbrough to the Isle of Man, to Catania, and some conferences in Belgium and The Netherlands. It was a pleasure doing all of them. I had food poisoning in one of them, Took an 11-hour train ride due to problems, and even had to update demos an hour before a workshop because Chrome Canary updated (once again, thank You Luke Warlow for helping on that one).</p> <p>I’m always honest about this… I stress, every time, whether it’s for 20 people or 200 people. But I believe that’s a good thing, as long as I stress about it, it means that I want to give attendees everything I got, with correct info and a laugh or two.</p> <p>The people I meet during those conferences, that’s probably the best thing about it. I meet so many interesting people. From people I look up to (and be star-struck when meeting them) to people who just do the most amazing things such as teaching kids and making a difference in the world. And yes, visiting a few new places while I’m at it is a great bonus of course.</p> <p>I did about 10 presentations (small and big), but I still don’t consider myself a public speaker… I feel like the guy who writes some stuff that wants to share a little story. So what’s next?</p> <picture> <source srcset="/_astro/middlesbrough.BjgmXtkw_28px2m.avif 320w, /_astro/middlesbrough.BjgmXtkw_1KXOBs.avif 480w, /_astro/middlesbrough.BjgmXtkw_2rVRf7.avif 800w, /_astro/middlesbrough.BjgmXtkw_2oU8Qq.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/middlesbrough.BjgmXtkw_Z1x4YS6.webp 320w, /_astro/middlesbrough.BjgmXtkw_Z1TvHj0.webp 480w, /_astro/middlesbrough.BjgmXtkw_Z1dxEFl.webp 800w, /_astro/middlesbrough.BjgmXtkw_Z1gzo42.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/middlesbrough.BjgmXtkw_1sHmsF.jpg" srcset="/_astro/middlesbrough.BjgmXtkw_Ac4up.jpg 320w, /_astro/middlesbrough.BjgmXtkw_dKm4v.jpg 480w, /_astro/middlesbrough.BjgmXtkw_TIoHa.jpg 800w, /_astro/middlesbrough.BjgmXtkw_QGFjt.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Brecht presenting at Middlesbrough Front End with a purple slide behind him that has the text: the basics of creating a good UI" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <h3 id="next-year-speaking">Next year: speaking.</h3> <p>I’ll keep doing it and I have a few announcements soon. First up: NDC in London at the end of January.</p> <p>I’m mostly giving my Open UI talk about popovers, invokers, and the upcoming customizable select. It seems to be a bit hot topic at the moment and I love talking about it, but I have some other presentations I’d like to share with people. We’ll see… no real resolutions on this.</p> <h2 id="books-read-in-2024">Books read in 2024</h2> <p>I started reading again in 2024. Although not that much I did read a few books:</p> <ul> <li>Solve for happy - Mo Gawdat</li> <li>how to win friends and influence people - Dale Carnegie</li> <li>Ted Talks - Chris Anderson</li> <li>the subtle art of not giving a f*ck - Mark Manson</li> <li>Problemen die er geen zijn - Herman Brusselmans</li> </ul> <p>I usually like non-fiction books, there is one exception in this list, a book with some short stories by Herman Brusselmans. The reason I don’t like reading big novels is time and distractions…</p> <p>I didn’t really like “How to win friends and influence people”, even though this is one people call a classic. I’d recommend each and every other one on this list.</p> <h3 id="future-books">Future books!</h3> <ul> <li>Engineering management for the rest of us - Sarah Drasner</li> <li>Bounce - Matthew Syed</li> <li>The Last Wish - Andrzej Sapkowski</li> </ul> <p>Long overdue to read the book by Sarah Drasner, looking forward to it. Bounce came recommended and for my short stories, I’ll be reading a bit about The Witcher… loved the games, and the series, should at least try and read the books a bit.</p> <h2 id="2024-was-good-highlights">2024 was good, highlights.</h2> <ul> <li>My father got a bit better from cancer. That’s the number one on the list right there.</li> <li>Me and the wife took our first trip by plane together with our daughter. A trip to Lisbon and it was fantastic.</li> <li>During my job, I got the chance to work on improving the developer experience and community inside the company. It’s a work in progress, but I love doing it.</li> <li>The people I met, and those I haven’t had the pleasure of meeting.</li> <li>The work done in W3C Community groups (CSS-Next, Open-UI). Kudos to the new CSS logo!</li> </ul> <p>My daughter loves taking pictures… I love doing the same thing, I think mom felt a bit lonely from time to time with those two amateur photographers</p> <picture> <source srcset="/_astro/eva-lisbon.BPAUMf0C_q9PuU.avif 320w, /_astro/eva-lisbon.BPAUMf0C_1Svfcc.avif 480w, /_astro/eva-lisbon.BPAUMf0C_19Qnfh.avif 800w, /_astro/eva-lisbon.BPAUMf0C_28KFgf.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/eva-lisbon.BPAUMf0C_IzC1I.webp 320w, /_astro/eva-lisbon.BPAUMf0C_2bV1I0.webp 480w, /_astro/eva-lisbon.BPAUMf0C_1sh9L5.webp 800w, /_astro/eva-lisbon.BPAUMf0C_2rbrM3.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/eva-lisbon.BPAUMf0C_1hS5SW.jpg" srcset="/_astro/eva-lisbon.BPAUMf0C_ZSA2lu.jpg 320w, /_astro/eva-lisbon.BPAUMf0C_yKmkM.jpg 480w, /_astro/eva-lisbon.BPAUMf0C_Z9SuB8.jpg 800w, /_astro/eva-lisbon.BPAUMf0C_O0MoP.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="My daughter taking pictures of azuleas in Lisbon in an ol cloister" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <h2 id="so-no-real-new-year-resolutions">So… no real New Year resolutions?</h2> <p>There are, but this time they aren’t that web-related. My focus will still be on family. But to give you an idea here are the resolutions:</p> <ul> <li><strong>Losing weight</strong>… yes, a typical one, but I’m going for it this time. Even though I am quite active when it comes to sports, It’s hard… My genes don’t even allow me to look at a pizza without gaining a pound.</li> <li><strong>Learn to draw better</strong> My daughter loves drawing and coloring. I’m going to join her. I never was any good at drawing and I’m going to try and get better, together with her.</li> <li><strong>Write better</strong> Yes… handwriting… a lost art. Improve it!</li> <li><strong>Journaling</strong> In 2024 I started journaling. I love doing it, and now that I look at my little book at the end of the year, it’s a real treasure. I want to do it again, but better and a bit more consistent. It was a good first run though.</li> <li><strong>Get my “GVB” - Golf licence</strong></li> <li><strong>Be a bit less social media awkward</strong> - I really should pay more attention to socials…</li> <li><strong>Tell more people I love them</strong> (period)</li> </ul> <p>We already started on the drawing part!</p> <picture> <source srcset="/_astro/drawing.DF_zVjMM_ZCwgBO.avif 320w, /_astro/drawing.DF_zVjMM_Z1R99Wu.avif 480w, /_astro/drawing.DF_zVjMM_29DEEy.avif 800w, /_astro/drawing.DF_zVjMM_Z1Wb50S.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/drawing.DF_zVjMM_Z14WfvC.webp 320w, /_astro/drawing.DF_zVjMM_Z2jz8Qi.webp 480w, /_astro/drawing.DF_zVjMM_1HdFKK.webp 800w, /_astro/drawing.DF_zVjMM_Z2oB3TG.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/drawing.DF_zVjMM_GS2LJ.jpg" srcset="/_astro/drawing.DF_zVjMM_2jaPo0.jpg 320w, /_astro/drawing.DF_zVjMM_14xW3k.jpg 480w, /_astro/drawing.DF_zVjMM_19CQr.jpg 800w, /_astro/drawing.DF_zVjMM_Yw1YV.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A bunch of drawings, includes a duck and mouse in a cute manner, next to it you see a child's arms drawing/colouring some cute animals" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>When it comes to web-related things. Keep doing what I’m doing for now. Maybe learn a new programming language (If I find the time) but I definitely want to spend some more time for the W3C communities which might come at the cost of some articles, we’ll see.</p> <p>I hope 2025 will be great. Every year has its ups and downs, but there were many positives in 2024 so the bar is set quite high. Even though the resolutions list isn’t small, it’s mostly about some lifestyle changes. But in general, I just want a bit more of the same, nothing wrong with that, right?</p> <h2 id="what-i-wish-for">What I wish for</h2> <p>A world with less conflict… Not only less war in the world but also less screaming and more decent conversation. The term “woke” already has a negative ring to me because it makes me think of people screaming instead of conversing. Those screaming people should know that sometimes, they actually do more harm to their cause than helping the world move forward. I know that in the end, screaming might be all that is left to do, but really, for the sake of a better world:</p> <blockquote>Converse, be patient, converse some more, scream later</blockquote> <p>I also wish that we for a free web because the web belongs to everyone. A better web, that is more performant and accessible.</p> <blockquote>It’s the World Wide Web, not the Wealthy Western Web. <cite>- Bruce Lawson</cite></blockquote> <h2 id="special-thanks">Special thanks</h2> <p>To you… the person reading this.</p> <p>I am grateful, the list is long. I hope that most people that should know…know. If not, well, it’s in my 2025 resolutions.</p> <p>But now, it’s time to close off and have a great start to the New Year with some friends.</p> <p>Wherever you are, no matter which part of the world, no matter which holiday you were celebrating, I hope you are safe, that you have the warmth of family and/or friends around you, and that if you had a bad year, the year switch makes you hopeful and gives you energy, if you had a great year, the next will be just as well.</p> <p><strong>Happy New Year!</strong><br>Brecht</p> <small> (Credits: I went a bit lazy crating this articles visual by using a fireworks svgs created by <a href="https://freepik.com" target="_blank" rel="noreferrer noopener">freepik.com</a></small>Brecht De RuyteStylish holidays! Creating a scroll-driven Christmas tree in CSShttps://utilitybend.com/blog/stylish-holidays-creating-a-scroll-driven-christmas-tree-in-css/https://utilitybend.com/blog/stylish-holidays-creating-a-scroll-driven-christmas-tree-in-css/Every year, I create a little demo right before the holidays as a CSS greeting card. This year is no different. To celebrate the end of the year and all those new amazing features that came into CSS, I decided to let myself go a little with new CSS techniques. A sort of recap of things I learned and put them in one little CodePen. This time, I’m combining scroll-driven animations, anchoring, and @property in CSS.Tue, 17 Dec 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-holidays.Ky0tq9qU_IgTMX.avif 375w, /_astro/visual-holidays.Ky0tq9qU_Z2iQEAs.avif 480w, /_astro/visual-holidays.Ky0tq9qU_SlO7b.avif 680w, /_astro/visual-holidays.Ky0tq9qU_1lqg0s.avif 800w, /_astro/visual-holidays.Ky0tq9qU_1BN1BC.avif 980w, /_astro/visual-holidays.Ky0tq9qU_2gLS6P.avif 1024w, /_astro/visual-holidays.Ky0tq9qU_Z8NTco.avif 1660w, /_astro/visual-holidays.Ky0tq9qU_1MIQVb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-holidays.Ky0tq9qU_Z1PtjII.webp 375w, /_astro/visual-holidays.Ky0tq9qU_czeFM.webp 480w, /_astro/visual-holidays.Ky0tq9qU_Z1Foppv.webp 680w, /_astro/visual-holidays.Ky0tq9qU_Z1djXwe.webp 800w, /_astro/visual-holidays.Ky0tq9qU_ZVWcU4.webp 980w, /_astro/visual-holidays.Ky0tq9qU_Z16Ikdu.webp 1024w, /_astro/visual-holidays.Ky0tq9qU_1xR1hd.webp 1660w, /_astro/visual-holidays.Ky0tq9qU_Z1r911i.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-holidays.Ky0tq9qU_XvT8M.jpg" srcset="/_astro/visual-holidays.Ky0tq9qU_1feuI5.jpg 375w, /_astro/visual-holidays.Ky0tq9qU_Z1LT4Fl.jpg 480w, /_astro/visual-holidays.Ky0tq9qU_1pjp2i.jpg 680w, /_astro/visual-holidays.Ky0tq9qU_1RnPUz.jpg 800w, /_astro/visual-holidays.Ky0tq9qU_28KBwJ.jpg 980w, /_astro/visual-holidays.Ky0tq9qU_Z1OeqDl.jpg 1024w, /_astro/visual-holidays.Ky0tq9qU_PlTQm.jpg 1660w, /_astro/visual-holidays.Ky0tq9qU_1R2qUW.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A christmas tree with text underneath it: stylish holidays" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="svg-scroll-driven-animations-anchoring-property-offset-path-and-container-style-queries-combined">SVG, scroll-driven animations, anchoring, @property, offset-path, and container style queries combined</h2> <p>This year, I went for a Christmas tree with a whole bunch of goodies in it. That does mean that I had to drop a bit of browser support for it. This year, my Chrismas greetings are Chromium only… sorry 🙁</p> <p>The idea behind this article is more about my thoughts and technique behind it, but for how each and every one of those CSS features works, I’ll drop a bunch of links at the end of this article.</p> <p>But first a video of the finished product, or you can <a href="#the-demo">click here to go to the CodePen at the bottom right away</a>.</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A christmas tree being build on scroll"><source src="/_astro/stylish-holidays.BewWDK3m.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="the-setup-a-bunch-of-svg-work">The setup: A bunch of SVG work.</h2> <p>The first thing I had to do was to create my assets. I had 2 single path drawings. One of the tree and one for the baubles. The inside of the baubles were created later on, but the important thing was that they were the same shape and size, to keep my coding consistent.</p> <p>I knew I was going to animate the strokes of these SVGs with <code>stroke-dasharray</code> and <code>stroke-dashoffset</code>, so I needed to get those values. I used Illustrator to create my assets, and if you want to know the correct length for these values you can go to <strong>window &gt; document info</strong>. Inside that panel, you can click the hamburger menu icon and you will find <strong>objects</strong>. The first thing you will get to see is the path info that holds a length.</p> <p>I had to set these values as variables and I knew I was going to do some animating with them inside of a single timeline, this is why I chose the <code>@property</code> syntax right away.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --o-tree</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 1031.69<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --o-ball</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 148.443<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="setting-up-html-the-star-that-follows">Setting up HTML: The star that follows…</h2> <p>I’m not going to go through everything inside of the HTML, but this is the basic outline (explainer below):</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>tree-wrapper<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>svg-items<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>ball ball__3<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__path<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token property">stroke</span><span class="token punctuation">:</span><span class="token function">var</span><span class="token punctuation">(</span>--color-ball-3<span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__inner<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</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">&quot;</span>ball ball__2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__path<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token property">stroke</span><span class="token punctuation">:</span><span class="token function">var</span><span class="token punctuation">(</span>--color-ball-2<span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__inner<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</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">&quot;</span>ball ball__1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__path<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token property">stroke</span><span class="token punctuation">:</span><span class="token function">var</span><span class="token punctuation">(</span>--color-ball-1<span class="token punctuation">)</span><span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>ball__inner<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</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">&quot;</span>tree<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token function">var</span><span class="token punctuation">(</span>--tree-color<span class="token punctuation">)</span></span><span class="token punctuation">&quot;</span></span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>star<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>spark<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <ul> <li>The <code>tree-wrapper</code>, will be my single point of animation, passing through custom properties to all its children</li> <li>We have 3 <code>ball__path</code> items that have the exact same stroke, those are the outlines, the <code>ball__inner</code> items are purely the decorations which will fade in at the end.</li> <li>The tree has a single path containing the tree outline</li> <li>Finally, we have our <code>.star</code> that will follow along the same path as the <code>stroke-dashoffset</code> with <code>offset-distance</code>. We will attach the star to the correct elements by using anchor positioning.</li> </ul> <p>You might notice that I’ve used some lazy inline CSS with variables on my SVGs. I just set some colors for the strokes on my root:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--tree-color</span><span class="token punctuation">:</span> #1d5b2d<span class="token punctuation">;</span> <span class="token property">--color-ball-1</span><span class="token punctuation">:</span> #5386e4<span class="token punctuation">;</span> <span class="token property">--color-ball-2</span><span class="token punctuation">:</span> #e072a4<span class="token punctuation">;</span> <span class="token property">--color-ball-3</span><span class="token punctuation">:</span> #0e7c7b<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I will break this demo down as much as possible without getting lost in details.</p> <h2 id="the-tree-and-baubles">The tree and baubles</h2> <p>We have a bunch of offsets that we will need to animate, these are set with <code>@property</code>, we will set the initial value to 0. The starting points of these are already declared by <code>--o-tree</code> and <code>--o-ball</code></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --dash-offset-tree</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --dash-offset-ball-1</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --dash-offset-ball-2</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --dash-offset-ball-3</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Presentation styles aside, the tree itself has the following CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.tree</span> <span class="token punctuation">{</span> <span class="token property">stroke-dasharray</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-tree<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">stroke-dashoffset</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--dash-offset-tree<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --tree<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Here you see that we set the <code>stroke-dasharray</code> to the value we set in <code>--o-tree</code>, the <code>stroke-dashoffset</code> will start with the previously set base-value, but we will update that with the timeline to end at <code>0</code>. We also set an <code>anchor-name</code>, this is where the <code>.star</code> will be aligned to. We actually do pretty much the same for all of the baubles, here is an example of the first one:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.ball__1</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --ball-1<span class="token punctuation">;</span> <span class="token selector">.ball__path</span> <span class="token punctuation">{</span> <span class="token property">stroke-dasharray</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-ball<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">stroke-dashoffset</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--dash-offset-ball-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Just like before, this gets a unique <code>anchor-name</code> and the path containing the bauble outline will get the <code>stroke-dashoffset</code> animations.</p> <h2 id="the-wrapper-that-passes-everything">The wrapper that passes everything</h2> <p>It’s time we talk about the <code>.tree-wrapper</code>. As this is our single point of passing through custom properties. Once again positioning and presentation styles aside, this is the code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.tree-wrapper</span> <span class="token punctuation">{</span> <span class="token property">--star-anchor</span><span class="token punctuation">:</span> --tree<span class="token punctuation">;</span> <span class="token property">--dash-offset-tree</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-tree<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--dash-offset-ball-1</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-ball<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--dash-offset-ball-2</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-ball<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--dash-offset-ball-3</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-ball<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--scroll-me-opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> story auto linear<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> --page-scroller<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We will set the position of the star to the <code>--tree</code> anchor as this is the starting point. The <code>dash-offset</code>s are set equally to their maximum offset size, we will update those while scrolling to <code>0</code>.</p> <p>Finally, we call our story animation and set the <code>animation-timeline</code> to <code>--page-scroller</code>. This can be found in my root.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">html</span> <span class="token punctuation">{</span> <span class="token property">scroll-timeline</span><span class="token punctuation">:</span> --page-scroller block<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I know this isn’t necessary as it is the root scroller I’m using, but I like a named timeline.</p> <h2 id="the-star">The star</h2> <p>The custom properties for the star are the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --star-offset</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</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> 0%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This will control the star offset, aka, the animation path it will follow.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --star-opacity</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;number&gt;&quot;</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> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This will update the opacity from time to time, right between jumping between anchors</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --star-transform</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;transform-function&gt;+ | none&quot;</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> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This is a property for a space-separated transform list. This is used at the end to enlarge the star.</p> <p>The star itself:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.star</span> <span class="token punctuation">{</span> <span class="token property">--ball-path</span><span class="token punctuation">:</span> <span class="token function">path</span><span class="token punctuation">(</span>..<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--star-anchor<span class="token punctuation">)</span> top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--star-anchor<span class="token punctuation">)</span> left<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--star-opacity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">offset-distance</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--star-offset<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--star-transform<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">offset-path</span><span class="token punctuation">:</span> <span class="token function">path</span><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 function">style</span><span class="token punctuation">(</span><span class="token property">--star-anchor</span><span class="token punctuation">:</span> --ball-1<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">offset-path</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--ball-path<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 function">style</span><span class="token punctuation">(</span><span class="token property">--star-anchor</span><span class="token punctuation">:</span> --ball-2<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">offset-path</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--ball-path<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 function">style</span><span class="token punctuation">(</span><span class="token property">--star-anchor</span><span class="token punctuation">:</span> --ball-3<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">offset-path</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--ball-path<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Woah, a lot is happening here, I know, bear with me. Firstly, I set a general <code>--ball-path</code> variable, which contains the path of the bauble outline. The actual default <code>offset-path</code> property on the star is the same as the tree path. Also, notice that we are setting <code>--star-anchor</code>, this contains <code>--tree</code> as a starting name, but we will update that custom property during the animation, switching it from <code>--tree</code>, to <code>--ball-1</code>, <code>--ball-2</code>, etc.</p> <p>In those cases, the <code>offset-path</code> should be the shape of a bauble, which is why we use style queries to set the <code>offset-path</code> to that shape when the anchor changes.</p> <h2 id="the-timeline">The timeline</h2> <p>To create my scroll, I just set the body to a <code>min-height</code> of <code>500vh</code>. The timeline itself might be a bit much…, but I enjoyed making this. You can view the full timeline in the CodePen below. But here is a short explanation of what happens in the timeline:</p> <ol> <li>The star keeps the <code>opacity</code> of <code>1</code> for the start of the animation</li> <li>Meanwhile, the anchor of the star is set to the <code>--tree</code> and the offset is placed to <code>100%</code>, creating the animation along the path.</li> <li>Between that <code>1%</code> the star fades-out</li> <li>From <code>0%</code> to <code>25%</code>, the <code>stroke-dashoffset</code> of the tree gets animated from the start value to <code>0</code>.</li> <li>Very quickly (<code>25.1%</code>), the stars offset gets reset and the anchor is shifted to <code>--ball-1</code>, also triggering that style query we wrote before an setting the correct <code>offset-path</code>. The <code>stroke-dashoffset</code> of the ball is set to the initial value of <code>148.443</code>, ready to be animated to <code>0</code> as well.</li> <li>The star fades in and stays like that in for some time</li> <li><code>50%</code>, we repeat this story to go over to <code>--ball-2</code>, with a reset of the anchor setting at <code>50.1%</code>,</li> <li>And we continue this loop till the end</li> </ol> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> story</span> <span class="token punctuation">{</span> <span class="token selector">24%</span> <span class="token punctuation">{</span> <span class="token property">--star-opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">25%</span> <span class="token punctuation">{</span> <span class="token property">--star-offset</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--star-anchor</span><span class="token punctuation">:</span> --tree<span class="token punctuation">;</span> <span class="token property">--star-opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">--dash-offset-tree</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">25.1%</span> <span class="token punctuation">{</span> <span class="token property">--star-offset</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--star-anchor</span><span class="token punctuation">:</span> --ball-1<span class="token punctuation">;</span> <span class="token property">--dash-offset-ball-1</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--o-ball<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">28%, 49%</span> <span class="token punctuation">{</span> <span class="token property">--star-opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">--star-offset</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--star-anchor</span><span class="token punctuation">:</span> --ball-1<span class="token punctuation">;</span> <span class="token property">--star-opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">--dash-offset-ball-1</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* ... */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="happy-holidays">Happy holidays!</h2> <p>Even though this animation won’t run off the main thread (because custom properties just don’t…), it was just so much fun creating this demo. I’ll probably write one more article this year for a recap of 2024. But I just wanted to share my CSS Christmas card for this year.</p> <p>Whichever holiday you are celebrating this end of the year, I wish you all a beautiful holiday season and a lot of love/warmth with family and friends.</p> <div id="the-demo" class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Stylish holidays!" src="https://codepen.io/utilitybend/embed/preview/qEWbgMR?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/qEWbgMR"> Stylish holidays!</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="related-articles">Related articles</h2> <h3 id="related-by-me">Related by me</h3> <ul> <li><a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">Let’s hang! An intro to CSS Anchor Positioning with basic examples</a></li> <li><a href="https://utilitybend.com/blog/animating-clip-paths-on-scroll-with-at-property-in-css">Animating clip paths on scroll with @property in CSS</a></li> <li><a href="https://utilitybend.com/blog/taking-a-closer-look-at-property-in-css">Taking a closer look at @property in CSS</a></li> <li><a href="https://utilitybend.com/blog/scroll-driven-animations-in-css-are-a-joy-to-play-around-with">Scroll driven animations in CSS are a joy to play around with!</a></li> </ul> <h3 id="shout-out">Shout out!</h3> <ul> <li><a href="https://scroll-driven-animations.style/">Scroll-driven-animations.style (your go-to place for all the info)</a></li> <li><a href="https://developer.chrome.com/docs/css-ui/style-queries">Getting started with style queries</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property">@property info on mdn</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/offset-path">offset-path info on mdn</a></li> <li><a href="https://css-tricks.com/svg-line-animation-works/">How svg line-animation works (even though a few years old, a great article!)</a></li> </ul>Brecht De RuyteAdded to my CSS reset: interpolate-size, the quality-of-life feature we all wanted at some pointhttps://utilitybend.com/blog/added-to-my-css-reset-interpolate-size-the-quality-of-life-feature-we-all-wanted-at-some-point/https://utilitybend.com/blog/added-to-my-css-reset-interpolate-size-the-quality-of-life-feature-we-all-wanted-at-some-point/How can I animate from 0 to auto? I have a fixed width and want it to transition to the width set by its children. How do I do that? These CSS questions have been asked many times on platforms such as Stackoverflow, and I’ve certainly done some searching for those exact questions. I can’t count the number of max-width hacks or JavaScript solutions I’ve used to transition from 0 to auto. No more! I’m done, and for now, I’m going full progressive enhancement. In this article, I’d like to cover some of the benefits you get from adding the new interpolate-size property in your CSS Reset.Tue, 12 Nov 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-keywords.qD8jDQog_24Jkw0.avif 375w, /_astro/visual-keywords.qD8jDQog_ZWoeRq.avif 480w, /_astro/visual-keywords.qD8jDQog_2eOePd.avif 680w, /_astro/visual-keywords.qD8jDQog_Z2nis5r.avif 800w, /_astro/visual-keywords.qD8jDQog_Z26UGth.avif 980w, /_astro/visual-keywords.qD8jDQog_Z1zykRy.avif 1024w, /_astro/visual-keywords.qD8jDQog_1520C9.avif 1660w, /_astro/visual-keywords.qD8jDQog_Z23Bm3d.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-keywords.qD8jDQog_Zu0T0G.webp 375w, /_astro/visual-keywords.qD8jDQog_1y2EoO.webp 480w, /_astro/visual-keywords.qD8jDQog_ZjUYGt.webp 680w, /_astro/visual-keywords.qD8jDQog_88rbN.webp 800w, /_astro/visual-keywords.qD8jDQog_ovcMX.webp 980w, /_astro/visual-keywords.qD8jDQog_77zB3.webp 1024w, /_astro/visual-keywords.qD8jDQog_Z2itcHb.webp 1660w, /_astro/visual-keywords.qD8jDQog_Zdi6bK.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-keywords.qD8jDQog_2cmNXk.jpg" srcset="/_astro/visual-keywords.qD8jDQog_Z2tudmO.jpg 375w, /_astro/visual-keywords.qD8jDQog_ZqqDWj.jpg 480w, /_astro/visual-keywords.qD8jDQog_Z2jpj3B.jpg 680w, /_astro/visual-keywords.qD8jDQog_Z1QkRak.jpg 800w, /_astro/visual-keywords.qD8jDQog_Z1zX6ya.jpg 980w, /_astro/visual-keywords.qD8jDQog_ZAnvNN.jpg 1024w, /_astro/visual-keywords.qD8jDQog_24cOFT.jpg 1660w, /_astro/visual-keywords.qD8jDQog_Z1YiM3r.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="so-whats-this-all-about">So what’s this all about?</h2> <p>Well, it’s about this little CSS snippet that I will be adding to my CSS reset from now on:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</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">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>That is, I will be adding this mostly for new projects. I’ll get back to maintaining some legacy projects later in this article. The thing that happens with this small snippet is that <code>interpolate-size: allow-keywords</code> sets a flag on a <code>:root</code> level, allowing you to transition from and to widths and heights that have a keyword value, for example: <code>auto</code>, <code>max-content</code>, <code>min-content</code>, <code>stretch</code>, etc…</p> <p>To say it in fancy words: It allows you to animate between an <em>intrinsic-size-keyword</em> and a <em>length-percentage</em>.</p> <p>At the time of writing this is only available in Chrome, which is why it’s best to add the <code>@supports</code> feature query.</p> <p>Take the following demo as an example and hover or focus the buttons:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Quick interpolate-size" src="https://codepen.io/utilitybend/embed/preview/MWMRYRv?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/MWMRYRv"> Quick interpolate-size</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>By setting the <code>interpolate-size</code> on a root level, I was able to animate the text of the buttons when hovering or focusing on them, by going from a fixed width to an auto width:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 4rem<span class="token punctuation">;</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> clip<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> width 0.35s ease<span class="token punctuation">;</span> <span class="token property">white-space</span><span class="token punctuation">:</span> nowrap<span class="token punctuation">;</span> <span class="token selector">&amp;:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">width</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>So this demo is actually not that much work, which is great, that’s the whole point of this demo.</p> <p>It just gave me the superpower to animate to by using the auto keyword.</p> <h2 id="you-can-choose-to-do-this-case-by-case-with-calc-size">You can choose to do this case by case with calc-size()</h2> <p>If you’re like me, you’re also maintaining some projects you initially created some years ago. In those cases, you might not feel “safe” in changing the default behavior, because in some cases things could break or create unwanted animations.</p> <p>In this case, the <code>calc-size()</code> function is a great idea. Instead of setting <code>interpolate-size: allow-keywords</code> on the root level, we could - based on the previous example - do the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token comment">/* same as before */</span> <span class="token selector">&amp;:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc-size</span><span class="token punctuation">(</span>auto<span class="token punctuation">,</span> size<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">calc-size</span><span class="token punctuation">(</span>auto<span class="token punctuation">,</span> 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>You will still need the <code>@supports</code> query for now as at the moment this is still only available to Chrome. Do note that this is <strong>Chrome first, not Chrome only</strong>. This is a CSS feature that has a spec and it will come to other browsers eventually. Which makes it the perfect progressive enhancement. If you open the demo in non-supported browsers it just doesn’t animate, and that’s fine in most cases.</p> <h2 id="some-great-use-cases-where-keyword-animation-can-speed-up-your-development">Some great use cases where keyword animation can speed up your development</h2> <p>In this section, I thought it would be nice to show where this “animating to size-keywords” can come in handy as a progressive enhancement.</p> <p>A fun idea could be to have cards that animate on click, whether that is horizontal or vertical (I just had a bit of fun here), the idea is pretty much the same the whole time. In this demo I wanted an image container to grow based on the <code>max-width</code> of a card. Of course, there are other ways we could do this, but this makes it so easy to write and manage.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="interpolate-size: allow-keywords width" src="https://codepen.io/utilitybend/embed/preview/ZEgVKNa?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/ZEgVKNa"> interpolate-size: allow-keywords width</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>This is the code in nutshell:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</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">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">interpolate-size</span><span class="token punctuation">:</span> allow-keywords<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token selector">.image</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> clip<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> width 1s ease<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card:has(button[aria-expanded=&quot;true&quot;]) .image</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token comment">/* set width auto when expanded */</span> <span class="token punctuation">}</span> <span class="token selector">.card</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 comment">/* ... */</span> <span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token function">clamp</span><span class="token punctuation">(</span>280px<span class="token punctuation">,</span> 80vw<span class="token punctuation">,</span> 600px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>What happens here is that the <code>.image</code> wrapper fills the space available (a space limited by the <code>max-width</code> of the card). We set this wrapper initially to zero and clip the overflow. When the button is pressed, we animate this <code>.image</code> wrapper to have a <code>auto</code> width that just fills the space. It can really help us to create cool effects, fast.</p> <p>Another case is where we can toggle a setting for a platform’s navigation to show text or icons only, something I’ve seen a few times before. This is in my opinion the perfect example where a user probably doesn’t care whether something animates or not.</p> <p>In the following demo, you’ll see just that. I also included an extra idea containing a collapse of a warning notification. Just to show some of those things you might encounter…</p> <p>The code is pretty much the same as before, so I won’t go into it, but maybe it can help you in the future:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="interpolate-size: allow-keywords examples" src="https://codepen.io/utilitybend/embed/preview/JjgeByg?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/JjgeByg"> interpolate-size: allow-keywords examples</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I believe in progressive enhancements, and as this is a simple flag to turn on and off, it’s neat that the behavior of a page can be different between browsers, but stays easily consistent in the browser of choice.</p> <h2 id="bonus-the-update-on-the-details-element-in-combination-with-interpolate-size">Bonus: The update on the details element in combination with interpolate-size</h2> <p>First, I thought I’d write a separate article on this, but it just fits so nicely in these examples. While I was starting to create demos on this, a great article was released on this: <a href="https://developer.chrome.com/blog/styling-details" target="_blank" rel="noreferrer noopener">more options for styling &lt;details&gt; by Bramus</a>. There are some really neat examples on this and I suggest you read it, but with eyes on the <code>interpolate-size</code> feature let me summarise it just a little bit.</p> <p>In my presentation “The future of UI is open” I talk a bit about <a href="https://open-ui.org/components/accordion.explainer/" target="_blank" rel="noreferrer noopener">exclusive accordions</a>. This gives you the possibility to turn the <code>&lt;details&gt;</code> element into an accordion by combining them using the <code>name</code> attribute. Here is a basic example of that:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-accordion<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span> This is an example <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- content --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-accordion<span class="token punctuation">&quot;</span></span> <span class="token attr-name">open</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span> Of an accordion <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- content --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Exclusive Accordion" src="https://codepen.io/utilitybend/embed/preview/gOZoXOE?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/gOZoXOE"> Exclusive Accordion</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Since then, something new was added and set to be released in Chrome 131. The ability to target the content of a <code>&lt;details&gt;</code> element instead of just the <code>&lt;summary&gt;</code>. This can be targetted by a pseudo-element named <code>::details-content</code>. When reading about this the first time, I thought the following would be enough to animate this (but there is a caveat):</p> <pre class="language-css" data-language="css"><code is:raw="" 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">::details-content</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">::details-content</span> <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">transition</span><span class="token punctuation">:</span> padding-block 0.3s<span class="token punctuation">,</span> height 0.3s ease<span class="token punctuation">,</span> content-visibility 0.3s ease allow-discrete<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> clip<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details[open]::details-content</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>Unfortunately, this was not enough. There is a (new) property that is being set on the <code>::details-content</code> pseudo-element: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility" target="_blank" rel="noreferrer noopener">content-visibility</a>. This property controls whether or not an element renders its contents. We will need to transition this property as well, and since this is a keyword, it’s a job for <code>transition-behavior: allow-keywords</code>.</p> <p><a href="https://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style">I’ve written some basics on transition-behavior before</a>, but I really should revisit it as the times when we can benefit from it seem to rise.</p> <p>This is the updated code, note that we don’t actually need to set the values of <code>content-visibility</code> as these are handled by the user-agent stylesheet, but we do need to set the ability to transition it:</p> <pre class="language-css" data-language="css"><code is:raw="" 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">::details-content</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">::details-content</span> <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">transition</span><span class="token punctuation">:</span> padding-block 0.3s<span class="token punctuation">,</span> height 0.3s ease<span class="token punctuation">,</span> content-visibility 0.3s ease allow-discrete<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> clip<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details[open]::details-content</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>Another thing to remember is that <code>allow-discrete</code> should come last in the <code>transition</code> shorthand.</p> <p>Here is the quick animated accordion I made (for Chrome 131):</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="details element content animation" src="https://codepen.io/utilitybend/embed/preview/PoMyBbj?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/PoMyBbj"> details element content animation</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I know this might be a bit basic, which is why I suggest you check out the article by Bramus, I love the horizontal cards in it. But once again, this feature is a complete progressive enhancement as in some browsers that don’t support it, it just won’t animate. Although the HTML part of it has a full browser support… In case a user would use a browser that doesn’t support the <code>name</code> attribute for <code>&lt;details&gt;</code>, the details elements would still work, although not in an accordion manner but as single collapses.</p> <h2 id="and-thats-it-just-add-it">And that’s it, just add it!</h2> <p>No need for big conclusions, I can only encourage you to set this in your CSS reset or reboot. This might be one of my favorite candidates for interop as well. It would be nifty to have this in complete browser support.</p>Brecht De RuyteWe should all invest more in smaller developer communities, or at least, try tohttps://utilitybend.com/blog/we-should-all-invest-more-in-smaller-developer-communities-or-at-least-try-to/https://utilitybend.com/blog/we-should-all-invest-more-in-smaller-developer-communities-or-at-least-try-to/As a GDE, I recently traveled to the Isle of Man. It’s probably not the first place you would think of when talking about conferences, meetups, and developer communities in general. You’ll probably think about motor races in narrow streets and beautiful hills. This event was once again an eye-opener for me to realize we should probably try to do more for smaller community groups whether that is government sponsorships, or just doing that - maybe literally - extra mile as a speaker.Wed, 23 Oct 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-iom.B890v2Vt_XBgYH.avif 375w, /_astro/visual-iom.B890v2Vt_Z1U9BXP.avif 480w, /_astro/visual-iom.B890v2Vt_2hQiNS.avif 680w, /_astro/visual-iom.B890v2Vt_2qnDSb.avif 800w, /_astro/visual-iom.B890v2Vt_Z1ETbTM.avif 980w, /_astro/visual-iom.B890v2Vt_CIHyA.avif 1024w, /_astro/visual-iom.B890v2Vt_ZnXdFt.avif 1660w, /_astro/visual-iom.B890v2Vt_1KMhaU.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-iom.B890v2Vt_1h23vv.webp 375w, /_astro/visual-iom.B890v2Vt_Z1BIPs2.webp 480w, /_astro/visual-iom.B890v2Vt_Z2tU3tf.webp 680w, /_astro/visual-iom.B890v2Vt_Z2lnHoW.webp 800w, /_astro/visual-iom.B890v2Vt_Z1mtpnY.webp 980w, /_astro/visual-iom.B890v2Vt_ZaypTn.webp 1024w, /_astro/visual-iom.B890v2Vt_Z1cgm9r.webp 1660w, /_astro/visual-iom.B890v2Vt_28yGxB.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-iom.B890v2Vt_ZLP7r2.jpg" srcset="/_astro/visual-iom.B890v2Vt_Zl8AQH.jpg 375w, /_astro/visual-iom.B890v2Vt_1PhCXG.jpg 480w, /_astro/visual-iom.B890v2Vt_X6pWt.jpg 680w, /_astro/visual-iom.B890v2Vt_16CL1L.jpg 800w, /_astro/visual-iom.B890v2Vt_25x42J.jpg 980w, /_astro/visual-iom.B890v2Vt_h0utm.jpg 1024w, /_astro/visual-iom.B890v2Vt_ZJGqKH.jpg 1660w, /_astro/visual-iom.B890v2Vt_SHU9F.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>When we think of conferences and Google DevFest, we usually think about the big cities and the huge events. And while I agree that those places are probably worth a lot in marketing material, I also strongly believe we should think more about the small events. This occurred to me in local meetups, but also in <a href="https://www.middlesbroughfe.co.uk/" target="_blank">Middlesbrough</a>, which was a medium-sized event, but at heart was a conference that tried to place the North of the UK on the map. The same passion and dedication were felt in the Isle of Man. A handful of people trying to bring developers together, showing the world, that they too have a community - maybe small - but brave and full of talent.</p> <p><strong>I know that if you’re a speaker, you are probably skimming this article as fast as possible, but I promise to make my point quickly and well before the end of this article.</strong></p> <p><a href="https://www.linkedin.com/in/harleygarrett/" target="_blank">Harley</a> and <a href="https://www.linkedin.com/in/darrenbarklie/" target="_blank">Darren</a>, are two fantastic people trying to place the Isle of Man on the map by starting a GDG and organizing a <a href="https://gdg.community.dev/events/details/google-gdg-douglas-presents-devfest-2024/" target="_blank">Google DevFest</a>. From my first interview with Harley, I felt the passion and the desire to grow. As a GDE we do get travel reimbursed by Google for DevFest events and even though it was almost 11 hours of travel, I decided to go to check out the community on this Isle and I say so without regrets. So instead of giving a full story, let me just answer a few questions:</p> <h2 id="even-if-my-hotel-is-sponsored-why-should-i-travel-to-an-event-so-far-away">Even if my hotel is sponsored, why should I travel to an event so far away?</h2> <p>Look, let’s be honest here, I know as a speaker we probably won’t pay ourselves to go and give a presentation. But if your flight and hotel are reimbursed why are you always aiming on the big ones? Why did you start speaking? Is it out of the inner ego trying to get yourself on video and getting likes and hits? I hope not, I sincerely hope you are doing it for a passion, a passion for sharing, a mission to inspire young people, getting to know new people and talk about tech (and maybe do some sightseeing while at it ;) ).</p> <p>And that’s exactly what you should do. I can guarantee that I hadn’t met anyone present at the Isle of Man before, and I had wonderful conversations. I heard people who hope to have a bigger community that grows. I saw very inspiring people from <a href="https://codeclub.im/" target="_blank" rel="noreferrer noopener">Code Club IoM</a> showing kids a way into coding for over 10 years out of pure passion. That… is golden.</p> <p>Yes, you might be speaking at about 40 people tops which is smaller than some meetups in bigger cities, but I’d rather talk to 40 passionate people, than 90 people who are mainly there for the free food. To be honest, for me, the talk barely mattered, I feel that this trip has made me grow, and left me inspired and energized to do more where possible.</p> <h2 id="and-if-that-doesnt-convince-you">And if that doesn’t convince you…</h2> <p>I can tell you, you will be treated wonderfully. Harley and Darren picked me up at the airport, bought me a fantastic meal and we had a fantastic chat the first day. I felt right at home. If you go to these kinds of places, you’re not a number, you are the person they trust to help them grow by giving community enthusiasts more hunger for these events in the future. Yes, don’t let the 40 people turn into a conclusion it doesn’t matter, maybe - just maybe - it might mean more.</p> <h2 id="so-please-make-the-extra-effort-if-youre-able">So please… make the extra effort if you’re able</h2> <p>Even if it takes you double the time for fewer people. Even if you’d have to transfer planes just to get there. If you can get your travel reimbursed, go for it. You can thank me later for this tip.</p> <p>Before I get a whole lot of people shouting bad experiences to me… yes, those might also exist and I might not have run into them as a “junior speaker”. But also make sure that when you go to these events, that you are humble. Do not act as the big shot speaker (that you probably are) but act like me, the junior guy with the imposter syndrome.</p> <h2 id="about-the-gdg-douglas---isle-of-man">About the GDG Douglas - Isle of Man</h2> <p>If you were just here for my rant, you can stop reading now. But I can’t just leave this article without writing a bit about my experience on the Isle of Man.</p> <p>On the first day after a long trip, GDG group organizers Darren and Harley from <a href="https://www.craftapplied.com/" target="_blank" rel="noreferrer noopener">Craft Applied</a> were waiting for me at the airport. After a drive, saying “hi fairies” when crossing the <a href="https://en.wikipedia.org/wiki/Fairy_Bridge_(Isle_of_Man)" target="_blank" rel="noreferrer noopener">fairy bridge</a> (mandatory) and a quick bag drop-off at the “Sefton” hotel (which was lovely), we went for a speakers’ dinner. This is where Felipe joined in, who is also an organizer, but more about him later. We had a lovely conversation about a lot of things and also talked a bit about shop, but we made it an early night. I was dead tired and they had to get some early sleep before the event.</p> <p>The morning after - the day of the event - I was awake way too early as always. So I took a walk around the Douglas while everyone was still sleeping. What a beautiful place!</p> <picture> <source srcset="/_astro/castle-in-the-sea.BtS6lk5U_ZT0bCD.avif 320w, /_astro/castle-in-the-sea.BtS6lk5U_Z2suF1F.avif 480w, /_astro/castle-in-the-sea.BtS6lk5U_2ltpN2.avif 800w, /_astro/castle-in-the-sea.BtS6lk5U_gWhqV.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/castle-in-the-sea.BtS6lk5U_2pRM7M.webp 320w, /_astro/castle-in-the-sea.BtS6lk5U_QniIK.webp 480w, /_astro/castle-in-the-sea.BtS6lk5U_AafJw.webp 800w, /_astro/castle-in-the-sea.BtS6lk5U_Z1tlRBz.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/castle-in-the-sea.BtS6lk5U_Z2u5CaR.jpg" srcset="/_astro/castle-in-the-sea.BtS6lk5U_2qrAVU.jpg 320w, /_astro/castle-in-the-sea.BtS6lk5U_QW7xS.jpg 480w, /_astro/castle-in-the-sea.BtS6lk5U_AJ4yE.jpg 800w, /_astro/castle-in-the-sea.BtS6lk5U_Z1sM3Mr.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A castle in the sea during blue hour" loading="lazy" decoding="async" fetchpriority="auto" width="40" height="30"> </picture> <h3 id="the-devfest-at-douglas">The DevFest at Douglas</h3> <p>I arrived on time, met up with everyone, and had a bit of coffee, you could feel the nerves of the organization a bit, it showed their passion for making this a success. Luckily one of the organizers Bransom Bean was an expert in lightening the mood. You just feel it, these people have one goal: bringing developers together from all over the Island.</p> <p>A bit later, <a href="https://twitter.com/domizajac?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor" target="_blank" rel="noreferrer noopener">Dominika Zając</a>, the first speaker of the event arrived. After just a brief introduction, I could feel her passion as a GDE. I knew right away this was going to be a perfect start to this event. It was a great talk on accessibility with examples, jokes, and memes, and it opened a lot of questions and discussions over the day. I’ve seen quite a few accessibility awareness talks, but never with this kind of positive energy and great spin on AI and DevTools. You could tell she knows her stuff and there was probably a lot more she could talk about when given the time.</p> <p>Next up was Guneet Mutreja on “Leveraging Machine Learning and Google Earth Engine for Satellite Data Processing”. As a webUI geek, this might’ve been a bit too far off my domain, but still, there were some great examples in there of city growth and global warming illustrated. Unfortunately, the audio wasn’t always perfect and I’m quick to lose attention when watching a video in a conference… Sorry Guneet, it’s not you, it’s me 🙂</p> <p>The next presentation was named “Is it still coding if there are no lines of code?” and this was an amazing talk given by <a href="https://www.linkedin.com/in/fceotto/" target="_blank">Felipe Ceotto</a> and <a href="https://www.linkedin.com/in/owencutajar/" target="_blank">Owen Cutajar</a>. What’s better to see than two passionate people getting kids into coding from their early years until their teenage years and beyond… They’ve been doing this for 10 years(!!!). It shocked me how casually they were saying that! I thought this was a major achievement in itself. So interesting to see how they evolve techniques in coding to keep kids to teenagers interested, from Scratch to Lego, to Unreal engine,…</p> <p>I needed this talk, We needed this talk, we need these kinds of people. These are the things that matter and I wish them many more years and successes to come. More info on <a href="https://codeclub.im/" target="_blank" rel="noreferrer noopener">https://codeclub.im/</a></p> <p>After some great lunch with sandwiches and desserts, it was my turn to talk about popovers, invokers, and customizable selects. I’ll cut this part of the story short as it feels wierd talking writing about myself, here is a picture:</p> <picture> <source srcset="/_astro/me-presenting.V08VljBZ_zWxeK.avif 320w, /_astro/me-presenting.V08VljBZ_bSody.avif 480w, /_astro/me-presenting.V08VljBZ_1Coh5s.avif 800w, /_astro/me-presenting.V08VljBZ_Z236Onb.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/me-presenting.V08VljBZ_WIWBr.webp 320w, /_astro/me-presenting.V08VljBZ_yENAf.webp 480w, /_astro/me-presenting.V08VljBZ_20aGs9.webp 800w, /_astro/me-presenting.V08VljBZ_Z1Fkp0u.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/me-presenting.V08VljBZ_Z1xVSyQ.jpg" srcset="/_astro/me-presenting.V08VljBZ_Zh6NLu.jpg 320w, /_astro/me-presenting.V08VljBZ_ZFaWMG.jpg 480w, /_astro/me-presenting.V08VljBZ_KjU4d.jpg 800w, /_astro/me-presenting.V08VljBZ_2a0Wpv.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Me standing in front of a slide in a basic setup, slide has text: what seems normal to you could be inspirational for others" loading="lazy" decoding="async" fetchpriority="auto" width="40" height="30"> </picture> <p>As a closure of the day, we went from solving problems with UI to Solving problems with AI by <a href="https://www.linkedin.com/in/chriskissack/" target="_blank">Chris Kissack</a>. It showcased many good ideas on how to make the best use of AI making your life easier but at the same time raised a variety of questions, and I loved how Chris tackled this. Let me be honest, after a year of conferences full of AI, it’s hard to create something fresh, and this presentation was something very welcome as it also illustrated some findings Chris had by working with young people, as well as the elderly. Had a lovely chat with him as well.</p> <p>So yes… Isle of Man has some great talent, and even if not the biggest community, a very heartwarming and dedicated one that asks great questions and opens beautiful conversations full of pure interest and respect.</p> <picture> <source srcset="/_astro/collage-event.FiiwHgky_Z1OCS1m.avif 320w, /_astro/collage-event.FiiwHgky_Z1CSmcP.avif 480w, /_astro/collage-event.FiiwHgky_ZKi9n2.avif 800w, /_astro/collage-event.FiiwHgky_Z2thCwA.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/collage-event.FiiwHgky_1ojxh6.webp 320w, /_astro/collage-event.FiiwHgky_1A445C.webp 480w, /_astro/collage-event.FiiwHgky_2sEgUq.webp 800w, /_astro/collage-event.FiiwHgky_JEMKR.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/collage-event.FiiwHgky_Z1seb6J.jpg" srcset="/_astro/collage-event.FiiwHgky_Z1tp7uf.jpg 320w, /_astro/collage-event.FiiwHgky_Z1hEAFI.jpg 480w, /_astro/collage-event.FiiwHgky_Zp4nPU.jpg 800w, /_astro/collage-event.FiiwHgky_Z283R0t.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A collage of people speaking at the DevFest in Douglas" loading="lazy" decoding="async" fetchpriority="auto" width="50" height="50"> </picture> <p>After the event, we all went for some drinks, which was lovely but then something happened. I received an email…</p> <h2 id="your-flight-has-been-canceled-due-to-storm-ashley">Your flight has been canceled due to “storm Ashley”</h2> <p>I was supposed to leave the day right after but that sure wasn’t going to happen. I contacted the travel agent and they helped me right away.</p> <p>I got an extra night and a new flight for Monday (Thank you Google and thank you to the worktrips.com team… really!) It took a little while to get finally sorted, which makes sense but they were so helpful, and patient an made me feel at ease. So I continued with my original plan of that evening and visited Summerhill Glen together with Dominika. She was already planning on staying Sunday as well and she offered me to do some sightseeing together. This Island is absolutely magical…</p> <picture> <source srcset="/_astro/summerhill-wide.a5F2CCwM_ZY2KKD.avif 320w, /_astro/summerhill-wide.a5F2CCwM_1jTVSh.avif 480w, /_astro/summerhill-wide.a5F2CCwM_CVHaz.avif 800w, /_astro/summerhill-wide.a5F2CCwM_Z17M8IX.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/summerhill-wide.a5F2CCwM_1zAkqf.webp 320w, /_astro/summerhill-wide.a5F2CCwM_Z1bD5IL.webp 480w, /_astro/summerhill-wide.a5F2CCwM_Z17mrRV.webp 800w, /_astro/summerhill-wide.a5F2CCwM_2c5P1s.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/summerhill-wide.a5F2CCwM_Z2ovaln.jpg" srcset="/_astro/summerhill-wide.a5F2CCwM_1pB42D.jpg 320w, /_astro/summerhill-wide.a5F2CCwM_Z1lCm7n.jpg 480w, /_astro/summerhill-wide.a5F2CCwM_Z16MD3N.jpg 800w, /_astro/summerhill-wide.a5F2CCwM_2cEDPA.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A woods that has a path and branches in color because of led lights everywhere, it looks like forest in rainbow colors" loading="lazy" decoding="async" fetchpriority="auto" width="30" height="40"> </picture> <p>After handling things with <a href="https://www.iodigital.com/en/home" target="_blank" rel="noreferrer noopener">iO</a>, I was happy that I could just take that extra day and enjoy it to the fullest. I decided to work on the airport on Monday during a long layover period.</p> <h3 id="an-extra-day---isle-of-man-there-is-something-magical-there">An extra day - Isle of Man, there is something magical there</h3> <p>Forget about the TT motor race for a second, there is a lot to see and do for tourists at the Isle of Man. The beautiful vistas, nature, steam train. I am grateful I got to spend this other day, and also thankful for - my partner in crime for a day - Dominika. Exploring is always more fun when you can share some laughs, I hope we run into each other at another event in the future, maybe I’ll give that karaoke a try (big maybe). Later in the evening, we met up with Darren and Harley again, they seemed well rested and happy, it was great to see.</p> <p>After a good nights rest, they drove us to the airport.</p> <p>Yes, it was a long travel and I had a 6-hour layover time due to the other route, but it was worth it. Will I go to the Isle of Man again any time soon? That I don’t know, but one thing is for sure…</p> <p>This event, the people, and the Island will always have a special place in my heart.</p> <picture> <source srcset="/_astro/collage.CmwYiNaU_Z1qIEX8.avif 320w, /_astro/collage.CmwYiNaU_Z2iaEaW.avif 480w, /_astro/collage.CmwYiNaU_ZR4CMz.avif 800w, /_astro/collage.CmwYiNaU_Z7JCos.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/collage.CmwYiNaU_1d8YlB.webp 320w, /_astro/collage.CmwYiNaU_lH08M.webp 480w, /_astro/collage.CmwYiNaU_1LN1wa.webp 800w, /_astro/collage.CmwYiNaU_2w81Uh.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/collage.CmwYiNaU_Z29WueE.jpg" srcset="/_astro/collage.CmwYiNaU_2eI3lF.jpg 320w, /_astro/collage.CmwYiNaU_1nh48Q.jpg 480w, /_astro/collage.CmwYiNaU_Z2gO3hH.jpg 800w, /_astro/collage.CmwYiNaU_Z1wu2SA.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A collage of vistas of Douglas and the Isle of man, a steam train, a selfie of me and dominika" loading="lazy" decoding="async" fetchpriority="auto" width="50" height="50"> </picture>Brecht De RuyteIs the sticky thing stuck? Is the snappy item snapped? A look at state queries in CSShttps://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css/https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css/Oh, how I wanted to write this title for some time! I have Googled these questions before when working with sticky positioning and scroll snapping. It looks like we are finally getting an answer to these questions in the form of CSS state queries and I have been eagerly waiting to play around with this since CSS Day 2023. In this article, I want to show a few first impressions and demos I created with state queries in CSS and Scroll Snap events in JavaScript.Tue, 17 Sep 2024 00:00:00 GMT<picture> <source srcset="/_astro/state-query-visual.BYIr8XyB_ucsOC.avif 375w, /_astro/state-query-visual.BYIr8XyB_138xwF.avif 480w, /_astro/state-query-visual.BYIr8XyB_27KYpT.avif 680w, /_astro/state-query-visual.BYIr8XyB_Z2c4GPX.avif 800w, /_astro/state-query-visual.BYIr8XyB_Z1ydD2i.avif 980w, /_astro/state-query-visual.BYIr8XyB_Z1Jzed8.avif 1024w, /_astro/state-query-visual.BYIr8XyB_Zhez5J.avif 1660w, /_astro/state-query-visual.BYIr8XyB_Z28TfLe.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/state-query-visual.BYIr8XyB_1CMVtz.webp 375w, /_astro/state-query-visual.BYIr8XyB_2bJ1bC.webp 480w, /_astro/state-query-visual.BYIr8XyB_Z1NOFJ5.webp 680w, /_astro/state-query-visual.BYIr8XyB_Z13tec1.webp 800w, /_astro/state-query-visual.BYIr8XyB_ZpCanl.webp 980w, /_astro/state-query-visual.BYIr8XyB_Z1XqFDc.webp 1024w, /_astro/state-query-visual.BYIr8XyB_Zv61vN.webp 1660w, /_astro/state-query-visual.BYIr8XyB_XP5BB.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/state-query-visual.BYIr8XyB_ZVInA1.jpg" srcset="/_astro/state-query-visual.BYIr8XyB_Z2oxS6q.jpg 375w, /_astro/state-query-visual.BYIr8XyB_Z1PBNon.jpg 480w, /_astro/state-query-visual.BYIr8XyB_ZKYmv9.jpg 680w, /_astro/state-query-visual.BYIr8XyB_ZCTX5.jpg 800w, /_astro/state-query-visual.BYIr8XyB_Cd8PA.jpg 980w, /_astro/state-query-visual.BYIr8XyB_Z2xLsP.jpg 1024w, /_astro/state-query-visual.BYIr8XyB_1pLRDy.jpg 1660w, /_astro/state-query-visual.BYIr8XyB_Z89AVq.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Three purple squares agains a pastel background, the middle square has some comic explosion lines on it and reads snap" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture> <p>For this article, I’d like to start by showing how we could use state queries in Chrome Canary for now, following up with the scroll snap events in JS. But first, a small warning that these are still <strong>experimental features</strong> at the moment of writing. To view the demos, you should be using <a href="https://www.google.com/chrome/canary/" target="_blank">Chrome Canary</a>, go to <code>chrome://flags</code>, and enable the <code>Experimental web platform features</code>. To support some quick reading of the article, videos of the demo will be provided as well.</p> <h2 id="the-basic-syntax-of-a-state-query">The basic syntax of a state query</h2> <p>The current implementation of a state query re-uses the <code>container-type</code> property in pretty much the same way as container queries. So to check the state, we need it to set that <code>container-type</code> in order to query the children inside of it.</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>the-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>the-query<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>For now, the state queries are all about <code>scroll-state</code> but this might change in the future. The first thing we’d have to do is set the <a href="https://drafts.csswg.org/css-conditional-5/#container-type" target="_blank" rel="noreferrer noopener">container-type</a> accordingly:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.the-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Now that this is set, we can use a state query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.the-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token selector">.the-query</span> <span class="token punctuation">{</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span>&lt;scroll-state-type&gt; <span class="token punctuation">:</span> &lt;scroll-state&gt;<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Do something with this based on .the-container state */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Ok, that’s enough high-level information, let’s get practical.</p> <h2 id="state-queries-for-sticky-positioning">State queries for sticky positioning</h2> <p>Is the sticky thing stuck? A lot of us have been there, we have a header navigation that should remain sticky and should only show a shadow from the moment it gets stuck. It always involved some sort of extra scripting to get this working, or we just put the shadow on there by default. But with state queries, all of this is over and we could just use CSS for it.</p> <p>Let’s set up the basic HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>topbanner<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- A top banner, non-sticky --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>header-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- header-container will be sticky --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</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">&quot;</span>container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>Some title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>nav</span><span class="token punctuation">&gt;</span></span><span class="token comment">&lt;!-- nav items --&gt;</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>nav</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- A lot of content with main and aside --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Let’s set up the <code>container-type</code> and the sticky positioning:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We want things to go a bit smoothly so let’s also set the header to have a <code>transition</code> for the <code>box-shadow</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">header</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 0.5s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Now that our <code>container-type</code> is set, all, that is left to do is to create our state-query and add a <code>box-shadow</code> tot the <code>header</code> when the <code>.header-container</code> is stuck:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">header</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 0.5s ease-out<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">stuck</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <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.6<span class="token punctuation">)</span> 0px 12px 28px 0px<span class="token punctuation">,</span> <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.1<span class="token punctuation">)</span> 0px 2px 4px 0px<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.05<span class="token punctuation">)</span> 0px 0px 0px 1px inset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>It’s nice that we can use some nesting for this as well. Do note that if this lands Chrome-first we might want to provide this as a progressive enhancement. We can do this with a feature query, and for this example, it might be a good idea to just handle this with a bit of CSS nesting to not repeat ourselves too much:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.header-container</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token selector">header</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> box-shadow 0.5s ease-out<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">stuck</span><span class="token punctuation">:</span> top<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <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.6<span class="token punctuation">)</span> 0px 12px 28px 0px<span class="token punctuation">,</span> <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.1<span class="token punctuation">)</span> 0px 2px 4px 0px<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.05<span class="token punctuation">)</span> 0px 0px 0px 1px inset<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>If you are wondering about the other values for the stuck query, they handle all directions, including logical property variants.</p> <p>All the query values for stuck:</p> <ul> <li>none</li> <li>top</li> <li>left</li> <li>right</li> <li>bottom</li> <li>inset-block-start</li> <li>inset-block-end</li> <li>inset-inline-start</li> <li>inset-inline-end</li> </ul> <p>With a few more bells and whistles, this is the final demo, I created:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Sticky headers with scroll-state query, checking if the sticky element is stuck" src="https://codepen.io/utilitybend/embed/preview/XWLQPOe?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/XWLQPOe"> Sticky headers with scroll-state query, checking if the sticky element is stuck</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/sticky-stuck-state.CdGZ7MtQ.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="state-queries-for-scroll-snapping">State queries for scroll-snapping</h2> <p>Is the snappy item snapped? From the moment<a href="https://www.youtube.com/watch?v=34zcWFLCDIc" target="_blank" rel="noreferrer noopener"> Adam Argyle gave his “Oh Snap!” presentation at CSS day 2022</a> it was one of the things missing. How can we know if an item is snapped? There was a great workaround with <code>IntersectionObserver</code>, but let’s be honest, it wasn’t the thing we wanted, there just wasn’t a better alternative. But now there is, in two forms actually, one JavaScript solution planned to be shipped in the next Chrome release that gives a lot more than just the snapped item, but also state queries.</p> <p>Since I started with state queries, let’s tackle that one first. The setup is pretty much the same as before, but let’s first create a scroll snap situation:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- And a few more of these list items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Let’s set up some quick and easy CSS to create our scroll-snapping situation:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.list</span> <span class="token punctuation">{</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 90vw<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<span class="token punctuation">;</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">padding-left</span><span class="token punctuation">:</span> 40vw<span class="token punctuation">;</span> <span class="token punctuation">}</span> <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-auto-flow</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 1vw<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> end<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 29vw<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 470px<span class="token punctuation">;</span> <span class="token selector">span</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">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 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">background</span><span class="token punctuation">:</span> slateblue<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> background 0.4s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>I know that this is a bit of a quick and dirty way to create the scroll-snapping situation, but this will create a good setup for our demo, you should have something like this:</p> <picture> <source srcset="/_astro/scroll-snap-state.CKocBtI3_7kfAm.avif 320w, /_astro/scroll-snap-state.CKocBtI3_Z1NzAr0.avif 480w, /_astro/scroll-snap-state.CKocBtI3_2ekYGf.avif 800w, /_astro/scroll-snap-state.CKocBtI3_1tUOmt.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/scroll-snap-state.CKocBtI3_1O1b4X.webp 320w, /_astro/scroll-snap-state.CKocBtI3_Z6SEWo.webp 480w, /_astro/scroll-snap-state.CKocBtI3_Z19adD5.webp 800w, /_astro/scroll-snap-state.CKocBtI3_Z1SznWQ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/scroll-snap-state.CKocBtI3_22rS9W.png" srcset="/_astro/scroll-snap-state.CKocBtI3_Zvpimb.png 320w, /_astro/scroll-snap-state.CKocBtI3_Z2rk9ox.png 480w, /_astro/scroll-snap-state.CKocBtI3_1AAqIH.png 800w, /_astro/scroll-snap-state.CKocBtI3_QbgoV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="All hearts are visible in the browser window, but unortunatly sticking to the top." loading="lazy" decoding="async" fetchpriority="auto" width="70" height="33"> </picture> <p>Now we want the item at the end to change <code>background-color</code> when it gets snapped.</p> <p>For this, we will add the scroll-state container query and set the scroll-state type to “snapped” followed up with the direction we want to snap, in this case, “inline”.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token comment">/* previous things */</span> <span class="token selector">span</span> <span class="token punctuation">{</span> <span class="token comment">/* default span styling */</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">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">container-type</span><span class="token punctuation">:</span> scroll-state<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@container</span> <span class="token function">scroll-state</span><span class="token punctuation">(</span><span class="token property">snapped</span><span class="token punctuation">:</span> inline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">span</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> lightcoral<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>By setting the <code>container-type</code> to the list-item we can check inside of its children whether this container is snapped or not, when we do that, we can do all sorts of things to those elements such as changing the <code>background-color</code>.</p> <p>With a bit of presentation styles, this is that demo:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-state query to check which item is snapped with CSS" src="https://codepen.io/utilitybend/embed/preview/xxoNyxY?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/xxoNyxY"> Scroll-state query to check which item is snapped with CSS</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/scroll-snap-state.CnKUJfR3.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>The query values for snapped are pretty straightforward:</p> <ul> <li>inline</li> <li>block</li> </ul> <h3 id="taking-it-a-step-further-and-combining-it-with-css-anchor-positioning">Taking it a step further and combining it with CSS anchor positioning</h3> <p>A few months ago, my daughter started to show interest in Pokémon. She then started watching the first Indigo League series.</p> <p>I felt inspired by this and wanted to create something fun for her based on the previous demo. The setup is pretty much the same as the previous demo, but let’s take a look at the result first:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll-state query to check which item is snapped with CSS, Pokemon version" src="https://codepen.io/utilitybend/embed/preview/MWMZoqp?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/MWMZoqp"> Scroll-state query to check which item is snapped with CSS, Pokemon version</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/scroll-snap-state-pokemon.2M7lPqRb.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>So quite a few animations are running when the item gets snapped, but the special case here is the frame that moves over the snapped item. To create this effect, we can use <a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">the newly available CSS anchoring positioning API</a>. Here is how that works:</p> <p>We re-use the <code>.list</code> we had before, but as a sibling to the <code>ul</code>, a <code>.frame</code> is placed:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- List items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</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">&quot;</span>frame<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Next up, set the <code>.list</code> to a <code>position: relative</code> and the <code>.frame</code> as <code>position: absolute</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.list</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> <span class="token selector">.frame</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 punctuation">}</span> </code></pre> <p>We’re going to set the position of the frame based on the snapped item, to do this (and remember, the basic setup is almost the same as the previous demo), we add the following to our state query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">li span</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">container-type</span><span class="token punctuation">:</span> scroll-state<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">scroll-state</span><span class="token punctuation">(</span><span class="token property">snapped</span><span class="token punctuation">:</span> inline<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --active-item<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Each time that our item gets snapped, the item gets an <code>anchor-name</code>, and all that is left for us to do is to position our <code>.frame</code> based on that <code>anchor-name</code> as well as set the sizing. We also add a little transition for the delay effect:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">.frame</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> <span class="token function">anchor</span><span class="token punctuation">(</span>--active-item top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>--active-item left<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">anchor-size</span><span class="token punctuation">(</span>--active-item width<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">anchor-size</span><span class="token punctuation">(</span>--active-item height<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> left 0.2s<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 10px ridge slategray<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>And that’s pretty much all it takes. If you’re unfamiliar with anchoring, I wrote an <a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">intro to CSS anchor positioning</a> a few months ago that you can check out.</p> <p>Keeping that Pokémon inspiration going, It’s time to talk about some net JavaScript events.</p> <h2 id="scroll-snapping-javascript-events">Scroll-snapping JavaScript events</h2> <p>Expected to come in <strong>Chrome 129</strong>, we are getting some new events for scroll-snapping:</p> <ul> <li><code>scrollsnapchanging</code></li> <li><code>scrollsnapchange</code></li> </ul> <p>This means that we will get these before state-queries in CSS. Let’s break them down:</p> <p>I re-created a list of Pokémon just as before, this time adding an id on my <code>.list</code>:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>list<span class="token punctuation">&quot;</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">&quot;</span>pokedex<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- bunch of list items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The list itself is what contains the <code>scroll-snap-type</code> property, this is important. In this demo though, we’re scrolling the Y-axis:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.list</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">max-height</span><span class="token punctuation">:</span> 60dvh<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> y mandatory<span class="token punctuation">;</span> <span class="token property">overflow-y</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h3 id="scrollsnapchanging">Scrollsnapchanging</h3> <p>The <code>scrollsnapchanging</code> event is dispatched multiple times while a user scrolls as new snap points are selected by the browser. At this moment the actual snap hasn’t occurred yet.</p> <h3 id="scrollsnapchange">Scrollsnapchange</h3> <p>The <code>scrollsnapchange</code> event is dispatched once a scroll completes, indicating the newly snapped element. Thus, the actual “snap” has occurred.</p> <h3 id="visualizing-the-difference">Visualizing the difference</h3> <p>In the video below, you can see my demo at work. I open up the Pokéballs on <code>scrollsnapchanging</code>, and reveal the Pokémon on <code>scrollsnapchange</code>.</p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/scroll-snap-js.BD53yZMt.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>Here is that code in a nutshell:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> listItems <span class="token operator">=</span> pokedex<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;li&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pokedex<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;scrollsnapchange&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> listItems<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">listItem</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> listItem<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">&quot;active&quot;</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> event<span class="token punctuation">.</span>snapTargetBlock<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;active&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">200</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> pokedex<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;scrollsnapchanging&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> listItems<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">listItem</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> listItem<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">&quot;changing&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;active&quot;</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> event<span class="token punctuation">.</span>snapTargetBlock<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;changing&quot;</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> <p>And here is that demo for you to play around with:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll snap events with JavaScript" src="https://codepen.io/utilitybend/embed/preview/XWLwRXV?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/XWLwRXV"> Scroll snap events with JavaScript</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>As a fantastic extra resource: <a href="https://codepen.io/web-dot-dev/pen/jOjaaEP" target="_blank" rel="noreferrer noopener">A great demo by web.dev can be found here, a great visualizer for scroll snap events</a></p> <h2 id="some-closing-thoughts-on-state-queries-and-the-lack-of-a-pseudo-class">Some closing thoughts on state-queries and the lack of a pseudo-class</h2> <p>So, state queries do pretty much what I hoped them to do, they certainly are a lot more versatile than adding pseudo-classes such as <code>:snapped</code> or <code>:stuck</code>. There is however one thing, especially for scroll snapping where I thought a pseudo-class might’ve been handy.</p> <p>A combination with <code>:has()</code> would’ve been handy to target the previous siblings of the snapped item for some visual effects, Eg:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">li:has(+li:snapped)</span> <span class="token punctuation">{</span> <span class="token comment">/* Might&#39;ve been handy */</span> <span class="token punctuation">}</span> </code></pre> <p>But of course, there are ways around that in the future with style-queries, especially if we go beyond custom properties for those, and let’s not forget that scroll-driven animations can help us a lot with visual effects.</p> <p>Even though I think it would’ve been a nice addition, I like this a bit better because it’s more multi-purpose, and updating the container-type for more purposes seems to make perfect sense.</p> <p>This is of course still experimental, but this is a real quality of life improvement I’m really looking forward to use, starting as a progressive enhancement.</p>Brecht De RuyteA spec update on CSS anchor positioninghttps://utilitybend.com/blog/a-spec-update-on-css-anchor-positioning/https://utilitybend.com/blog/a-spec-update-on-css-anchor-positioning/The CSS Working Group (CSSWG) has recently introduced changes to the anchor positioning spec in CSS. I've focused on anchoring in previous articles and to maintain accuracy, I will be updating an older article (just this once). This article will highlight the specific name changes and their corresponding Chrome release dates.Fri, 09 Aug 2024 00:00:00 GMT<picture> <source srcset="/_astro/anchoring-visual-updated.BL1mdw4z_9hrs2.avif 375w, /_astro/anchoring-visual-updated.BL1mdw4z_13qWfA.avif 480w, /_astro/anchoring-visual-updated.BL1mdw4z_1N3KSL.avif 680w, /_astro/anchoring-visual-updated.BL1mdw4z_1HIaM5.avif 800w, /_astro/anchoring-visual-updated.BL1mdw4z_Z1VjiTw.avif 980w, /_astro/anchoring-visual-updated.BL1mdw4z_2jNBCO.avif 1024w, /_astro/anchoring-visual-updated.BL1mdw4z_ZythMd.avif 1660w, /_astro/anchoring-visual-updated.BL1mdw4z_ZeV4Dc.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/anchoring-visual-updated.BL1mdw4z_Iw9V7.webp 375w, /_astro/anchoring-visual-updated.BL1mdw4z_1CFEIF.webp 480w, /_astro/anchoring-visual-updated.BL1mdw4z_2nitmQ.webp 680w, /_astro/anchoring-visual-updated.BL1mdw4z_2hWSga.webp 800w, /_astro/anchoring-visual-updated.BL1mdw4z_Z1m4Aqr.webp 980w, /_astro/anchoring-visual-updated.BL1mdw4z_Z6uQQn.webp 1024w, /_astro/anchoring-visual-updated.BL1mdw4z_25omww.webp 1660w, /_astro/anchoring-visual-updated.BL1mdw4z_E96hI.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/anchoring-visual-updated.BL1mdw4z_1tSg6E.jpg" srcset="/_astro/anchoring-visual-updated.BL1mdw4z_AwT2n.jpg 375w, /_astro/anchoring-visual-updated.BL1mdw4z_1uGoOV.jpg 480w, /_astro/anchoring-visual-updated.BL1mdw4z_2fjdt7.jpg 680w, /_astro/anchoring-visual-updated.BL1mdw4z_29XCmq.jpg 800w, /_astro/anchoring-visual-updated.BL1mdw4z_Z1u3Qkb.jpg 980w, /_astro/anchoring-visual-updated.BL1mdw4z_U4c8G.jpg 1024w, /_astro/anchoring-visual-updated.BL1mdw4z_Z1XdHhl.jpg 1660w, /_astro/anchoring-visual-updated.BL1mdw4z_1VLgp9.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A boats anchor agains a futuristic background with rectangle patterns, with text updated on it" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>Naming things is hard and that’s what this is all about, we were warned on CSS Day by Tab Atkins that this spec might still update, so here are the two changes:</p> <h2 id="change-1-position-try-options-becomes-position-try-fallbacks">Change 1: position-try-options becomes position-try-fallbacks</h2> <p>Starting from <strong>Chrome 128</strong> <code>position-try-options</code> will be renamed to <code>position-try-fallbacks</code>. I’ve already updated my examples to be compliant with the latest version of the spec. Yes, the examples might be broken a bit in the current stable Chrome version while writing this (127), but I do believe that this is the way forward so no use in keeping the old name for now.</p> <h2 id="change-2-inset-area-becomes-position-area">Change 2: Inset-area becomes position-area</h2> <p>Starting from <strong>Chrome 129</strong> (Current Canary version) the <code>inset-area</code> property will be renamed to <code>position-area</code>.</p> <p>Currently, I’ve updated my examples to support both syntaxes as Chrome 129 is a few more months away. You will now see those examples like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">inset-area</span><span class="token punctuation">:</span> top span-all<span class="token punctuation">;</span> <span class="token property">position-area</span><span class="token punctuation">:</span> top span-all<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This ensures that when <code>position-area</code> is not supported, it will fall back to <code>inset-area</code>. In due time, I will remove the <code>inset-area</code>.</p> <h2 id="updating-an-article-just-this-once">Updating an article… just this once</h2> <p>I usually do not update articles, but in this case, I will update my previous anchoring article. With a small disclaimer linking to this information you’re reading here. Mostly because it’s still a recent article that has a decent amount of traffic and I want to inform people correctly.</p> <p><a href="https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples">You can find the updated version of the CSS anchoring article here.</a></p>Brecht De RuyteIt’s Time To Talk About “CSS5”https://utilitybend.com/blog/its-time-to-talk-about-css5/https://utilitybend.com/blog/its-time-to-talk-about-css5/Have you ever wondered what happened after CSS3? The W3C CSS-Next community group is actively searching for better approaches for how we describe the evolution of CSS over time and identify feature sets as effectively as we did with CSS3 way back in 2009 — and you can help.Mon, 05 Aug 2024 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2024/08/time-to-talk-about-css5/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteTime for a little break - summer 2024https://utilitybend.com/blog/time-for-a-little-break-summer-2024/https://utilitybend.com/blog/time-for-a-little-break-summer-2024/The last few months have been crazy. I had a blast visiting conferences, speaking at meetups, and working on new things. But now it’s time to recharge some of those batteries and have some family time. In this article a little throwback of the last month or so and some of my holiday plans.Sun, 21 Jul 2024 00:00:00 GMT<picture> <source srcset="/_astro/article-vacationing.HJrCvZot_1cersM.avif 375w, /_astro/article-vacationing.HJrCvZot_1jPC4T.avif 480w, /_astro/article-vacationing.HJrCvZot_28D31i.avif 680w, /_astro/article-vacationing.HJrCvZot_1i32Oe.avif 800w, /_astro/article-vacationing.HJrCvZot_1I5gs1.avif 980w, /_astro/article-vacationing.HJrCvZot_1Qdf0X.avif 1024w, /_astro/article-vacationing.HJrCvZot_1Dy86A.avif 1660w, /_astro/article-vacationing.HJrCvZot_Z1btxhs.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/article-vacationing.HJrCvZot_Xn02I.webp 375w, /_astro/article-vacationing.HJrCvZot_15YaDP.webp 480w, /_astro/article-vacationing.HJrCvZot_1TLAAe.webp 680w, /_astro/article-vacationing.HJrCvZot_14bAoa.webp 800w, /_astro/article-vacationing.HJrCvZot_1udO1W.webp 980w, /_astro/article-vacationing.HJrCvZot_Z6exp8.webp 1024w, /_astro/article-vacationing.HJrCvZot_ZiSEjv.webp 1660w, /_astro/article-vacationing.HJrCvZot_ZRdpIi.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/article-vacationing.HJrCvZot_cQ0Pu.jpg" srcset="/_astro/article-vacationing.HJrCvZot_Z2aVeAQ.jpg 375w, /_astro/article-vacationing.HJrCvZot_Z23k3YJ.jpg 480w, /_astro/article-vacationing.HJrCvZot_Z1ewD3l.jpg 680w, /_astro/article-vacationing.HJrCvZot_Z257Dfp.jpg 800w, /_astro/article-vacationing.HJrCvZot_Z1E5pBC.jpg 980w, /_astro/article-vacationing.HJrCvZot_Z1deeXa.jpg 1024w, /_astro/article-vacationing.HJrCvZot_Z1pSlRx.jpg 1660w, /_astro/article-vacationing.HJrCvZot_ajWHe.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="1920" height="1080" class="img-fluid"> </picture><p>I need to be honest right off the bat here. I have been doing a bunch of conferences and meetups lately and noticed that I lost a bit of the time where I would’ve created some demos and played around with new things. I’m also very behind on all the articles in my RSS feed. The last week or so, I’ve been catching up on those a bit, but as this week has ended, it’s time for me to do a complete shutdown for a bit when it comes to writing, demoing, and presentation creations. It’s also time to read some of that non-web-related stuff I’ve postponed.</p> <h2 id="a-quick-throwback">A quick throwback</h2> <p>Let’s start with the beginning of June, I will not talk about the articles I’ve written as you can find those here, but I do want to look back at some of the awesome events. This piece will probably have the most pictures of myself in like…ever. But sure, why not I guess…</p> <h3 id="css-day-and-css-café">CSS Day and CSS Café</h3> <p>I’m always happy to return to CSS Day, so many people I get to see again or get to meet for the very first time. This is always a great one for me and feels like coming home. I did a <a href="https://techhub.iodigital.com/articles/this-was-css-day-2024-the-10th-edition" target="_blank" rel="noreferrer noopener">full article on CSS Day at the tech_hub</a> and they’re uploading the presentations weekly now. I want to encourage people to go to these conferences as you get to talk to a lot of people and you can take away so much more than just watching videos. So, for that reason, here is me probably trying to explain some Open UI demo I created to <a href="https://github.com/josepharhar" target="_blank" rel="noreferrer noopener">Joey Arhar</a> using hand gestures as he knows a lot more about how this stuff works than I do.</p> <figure><picture> <source srcset="/_astro/cssday.BZjTt3hP_EjpE0.avif 320w, /_astro/cssday.BZjTt3hP_Z1fuebq.avif 480w, /_astro/cssday.BZjTt3hP_rQDjx.avif 800w, /_astro/cssday.BZjTt3hP_Z9yAvw.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/cssday.BZjTt3hP_Z28FcAn.webp 320w, /_astro/cssday.BZjTt3hP_11Hhn8.webp 480w, /_astro/cssday.BZjTt3hP_Z2l7XUP.webp 800w, /_astro/cssday.BZjTt3hP_27CU32.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/cssday.BZjTt3hP_Z14MMbQ.jpg" srcset="/_astro/cssday.BZjTt3hP_1J9nDQ.jpg 320w, /_astro/cssday.BZjTt3hP_ZaEgbz.jpg 480w, /_astro/cssday.BZjTt3hP_1wGBjo.jpg 800w, /_astro/cssday.BZjTt3hP_Ugmtk.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Me talking to Joey at CSS day, both have coffee in hands." loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture><figcaption><small>(Photo by <a href="https://richardtheemling.com/" target="_blank" rel="noreferrer noopener">Richard Theemling for CSS Day</a>)</small></figcaption></figure> <h3 id="google-io-connect">Google I/O Connect</h3> <p>As a Google Developer Expert, I was invited to Google I/O Connect in Berlin. I absolutely had a fantastic time. There were some interesting Q&amp;A sessions and so many things to see and do. I got to hang out a bit with <a href="https://www.bram.us" target="_blank" rel="noreferrer noopener">Bramus</a> and <a href="https://una.im/" target="_blank" rel="noreferrer noopener">Una</a> and meet some Googlers who work on fantastic stuff such as <a href="https://www.tunetheweb.com/" target="_blank" rel="noreferrer noopener">Barry Pollard</a>, <a href="https://samdutton.com/" target="_blank" rel="noreferrer noopener">Sam Dutton</a>. So nice to have a chance to talk to them in-person.</p> <p>I had a demo about speculation rules by <a href="https://jlwagner.net/" target="_blank" rel="noreferrer noopener">Jeremy Wagner</a> who sold it to me like a pro (yes… I probably WILL write an article about that later this year). And of course, I had my colleague from Amsterdam <a href="https://www.davebitter.com/" target="_blank" rel="noreferrer noopener">Dave Bitter</a> with me, always a pleasure to hang out with him.</p> <p>The things at Google I/O are probably worth an article by itself, but for now, I’ll keep it to these brief words. I do have a head full of wonderful memories and hope to do this again next year.</p> <picture> <source srcset="/_astro/google-io.kN2l-pP-_2jyxgn.avif 320w, /_astro/google-io.kN2l-pP-_ZGJOcx.avif 480w, /_astro/google-io.kN2l-pP-_Z1w0VdW.avif 800w, /_astro/google-io.kN2l-pP-_Z26zTop.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/google-io.kN2l-pP-_ZbYKHL.webp 320w, /_astro/google-io.kN2l-pP-_1QS0Cf.webp 480w, /_astro/google-io.kN2l-pP-_12BSAP.webp 800w, /_astro/google-io.kN2l-pP-_s2Uqn.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/google-io.kN2l-pP-_Z1YpHHV.jpg" srcset="/_astro/google-io.kN2l-pP-_ZUbLu2.jpg 320w, /_astro/google-io.kN2l-pP-_18FYPY.jpg 480w, /_astro/google-io.kN2l-pP-_jpROz.jpg 800w, /_astro/google-io.kN2l-pP-_Zg95kS.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Me doing peace signes in front of an I/O logo with flowers" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <h3 id="middlesbrough-front-end">Middlesbrough Front End</h3> <p>I got to speak at <a href="https://www.middlesbroughfe.co.uk/" target="_blank" rel="noreferrer noopener">Middlesbrough Front End</a> this week. It was quite exciting for me as it was the first time that I had to give a presentation to so many people who are native English speakers. I was dead tired when getting there due to some delays with the train, But thankfully I met one of the speakers there, <a href="https://twitter.com/TodePond" target="_blank" rel="noreferrer noopener">Lu Wilson</a>, also known as <a href="https://www.todepond.com/" target="_blank" rel="noreferrer noopener">TodePond</a>. If you read this Lu… thank you for getting me through that train ride! For those that don’t know Lu, you probably should as they are working on something cool called <a href="https://www.tldraw.com/" target="_blank" rel="noreferrer noopener">tldraw</a>.</p> <p>But honestly, once I arrived, everything was perfect, the people managing the event were awesome and the other speakers were so friendly and kindhearted that I quickly forgot the delays and lack of sleep. I am so happy I got to meet them, I’ll be honest, I was a bit starstruck as well. I had some insightful food tips from <a href="https://csswizardry.com/" target="_blank" rel="noreferrer noopener">Harry</a>, was amazed by all the things <a href="https://roe.dev/" target="_blank" rel="noreferrer noopener">Daniel</a> worked on, and was blasted away by the entertainment surge <a href="https://whitep4nth3r.com/" target="_blank" rel="noreferrer noopener">Salma</a> ( I could never to Twitch, but really, I might start watching it a bit from time to time, I got convinced I guess). I also got to talk to <a href="https://michellebarker.co.uk/" target="_blank" rel="noreferrer noopener">Michelle</a> IRL, which is funny, because I’m a long-time reader of <a href="https://css-irl.info/" target="_blank" rel="noreferrer noopener">CSS-IRL</a>. Really, to all of them, it was an absolute blast.</p> <p>I also met Jake Archibald, I felt like a groupie “hey, I love your podcast”. But really, super friendly, and it was a pleasure.</p> <p>A lot of them are far more experienced than I am, and hearing from them they enjoyed my presentation (or even took notes) meant a lot to me. But in general, except for a little microphone beep at the start of my talk, I’m pretty happy, the attendees have been so good to me, and the organization as well.</p> <p>It’s in my nature to mostly remember what I didn’t do well and where to improve. But I went home satisfied, even though I still have a full list of pointers, I feel I did ok.</p> <p>I recommend this conference to everyone, and I hope it keeps existing. A beautiful conference, professionally handled in the North of England where you feel that community matters. I will be promoting this conference in the future because I believe this is an important one for the North UK community.</p> <p>Here’s a few pictures</p> <figure><picture> <source srcset="/_astro/me-presenting.-5ivoU1V_Z28MmrE.avif 320w, /_astro/me-presenting.-5ivoU1V_Z1hxeKF.avif 480w, /_astro/me-presenting.-5ivoU1V_12YKEU.avif 800w, /_astro/me-presenting.-5ivoU1V_1pQ9Xh.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/me-presenting.-5ivoU1V_ZJ5Kyb.webp 320w, /_astro/me-presenting.-5ivoU1V_79m7N.webp 480w, /_astro/me-presenting.-5ivoU1V_2rGmyo.webp 800w, /_astro/me-presenting.-5ivoU1V_Z2fDmWb.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/me-presenting.-5ivoU1V_1PNt6x.jpg" srcset="/_astro/me-presenting.-5ivoU1V_1obiOk.jpg 320w, /_astro/me-presenting.-5ivoU1V_2fqqvj.jpg 480w, /_astro/me-presenting.-5ivoU1V_ZudGR2.jpg 800w, /_astro/me-presenting.-5ivoU1V_Z7miyF.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Me presenting on the Middlesbrough front end stage, purple lights and slide" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture><figcaption><small>(<a href="https://htmlandbacon.com/" target="_blank" rel="noreferrer noopener">Photo by Colin</a>)</small></figcaption></figure> <p>And an after-event-drinks photo: FLTR: Lu Wilson, Me, Shawn Anuptra Martin, Michelle Barker, Ryan Varley.</p> <picture> <source srcset="/_astro/at-mfe.D7h0B0vw_Zy5rId.avif 320w, /_astro/at-mfe.D7h0B0vw_Z2sT6yD.avif 480w, /_astro/at-mfe.D7h0B0vw_ZKxe3F.avif 800w, /_astro/at-mfe.D7h0B0vw_Z1mXsSJ.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/at-mfe.D7h0B0vw_1I73Pl.webp 320w, /_astro/at-mfe.D7h0B0vw_ZbGA05.webp 480w, /_astro/at-mfe.D7h0B0vw_1vEhuS.webp 800w, /_astro/at-mfe.D7h0B0vw_Te2EO.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/at-mfe.D7h0B0vw_Z2icEz4.jpg" srcset="/_astro/at-mfe.D7h0B0vw_vJvgD.jpg 320w, /_astro/at-mfe.D7h0B0vw_Z1o48yM.jpg 480w, /_astro/at-mfe.D7h0B0vw_jhIVb.jpg 800w, /_astro/at-mfe.D7h0B0vw_Zi8uSS.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A group photo of Lu Wilson, Me, Shawn Anuptra Martin, Michelle Barker, Ryan Varley, some people have beers in hands" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>(PS, after the event, <a href="https://www.shawn-am.co.uk/blog/my-first-conference" target="_blank" rel="noreferrer noopener">Shawn started blogging, check it out here</a>.)</p> <h2 id="and-then-now-a-well-deserved-holiday">And then now: A well-deserved holiday</h2> <p>I’ve met so many people in the last month, it’s been absolutely bonkers…</p> <p>But for now, it’s time… Yes, time for family, to go on trips, to amusement parks, and to the zoo. Time to read that book I never got to. Enjoy our garden and create + eat tasty food.</p> <p>I also started some golf lessons, just out of curiosity which I will take during this summer.</p> <p>So yes, It will be packed with a lot of things to do, but when it comes to the web, I’ll mostly just be boosting some posts from time to time, not creating demos, not joining meetings, a bit of digital detox. I’ve already written an article that might still get published during my holiday, but that’s about it.</p> <p>So to everyone out there, enjoy your holidays, I know I will.</p> <picture> <source srcset="/_astro/how-music-works.Cer3aN4o_Z2bd7JG.avif 320w, /_astro/how-music-works.Cer3aN4o_XXCI4.avif 480w, /_astro/how-music-works.Cer3aN4o_ZlN6zN.avif 800w, /_astro/how-music-works.Cer3aN4o_uUijF.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/how-music-works.Cer3aN4o_Z1h7Vhz.webp 320w, /_astro/how-music-works.Cer3aN4o_1S3Obb.webp 480w, /_astro/how-music-works.Cer3aN4o_xh4Rj.webp 800w, /_astro/how-music-works.Cer3aN4o_1p0tLM.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/how-music-works.Cer3aN4o_2kxOCM.jpg" srcset="/_astro/how-music-works.Cer3aN4o_1EErXQ.jpg 320w, /_astro/how-music-works.Cer3aN4o_ZfkUmk.jpg 480w, /_astro/how-music-works.Cer3aN4o_Z1A7EFc.jpg 800w, /_astro/how-music-works.Cer3aN4o_ZIofKI.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A book on the table next to a blue coffee mug, the book title is How music works" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="160"> </picture>Brecht De RuyteAn update on invokers: Invoker commands in HTMLhttps://utilitybend.com/blog/an-update-on-invokers-invoker-commands-in-html/https://utilitybend.com/blog/an-update-on-invokers-invoker-commands-in-html/One thing I’ve learned over the last year is to deal with changes in explainers and specs and that naming things is hard. In this update on Invokers, I will cover the new syntax and the new name: Invoker Commands. Once again giving you some control over interaction with HTML from opening dialog elements to creating custom counter buttons and video controls.Mon, 15 Jul 2024 00:00:00 GMT<picture> <source srcset="/_astro/invoker-commands-visual.BdmT_e5s_Z1yyeGM.avif 375w, /_astro/invoker-commands-visual.BdmT_e5s_1USx7H.avif 480w, /_astro/invoker-commands-visual.BdmT_e5s_Z1WFigG.avif 680w, /_astro/invoker-commands-visual.BdmT_e5s_uxWLs.avif 800w, /_astro/invoker-commands-visual.BdmT_e5s_ZFyk2t.avif 980w, /_astro/invoker-commands-visual.BdmT_e5s_1mPx72.avif 1024w, /_astro/invoker-commands-visual.BdmT_e5s_ws90F.avif 1660w, /_astro/invoker-commands-visual.BdmT_e5s_eghcq.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/invoker-commands-visual.BdmT_e5s_1SHkdC.webp 375w, /_astro/invoker-commands-visual.BdmT_e5s_iWXeb.webp 480w, /_astro/invoker-commands-visual.BdmT_e5s_1uAgDI.webp 680w, /_astro/invoker-commands-visual.BdmT_e5s_Z17mB74.webp 800w, /_astro/invoker-commands-visual.BdmT_e5s_Z2itSV0.webp 980w, /_astro/invoker-commands-visual.BdmT_e5s_1W5fA7.webp 1024w, /_astro/invoker-commands-visual.BdmT_e5s_Z1SQktw.webp 1660w, /_astro/invoker-commands-visual.BdmT_e5s_Z2c3chL.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/invoker-commands-visual.BdmT_e5s_1dCqTr.jpg" srcset="/_astro/invoker-commands-visual.BdmT_e5s_1eq2OF.jpg 375w, /_astro/invoker-commands-visual.BdmT_e5s_Zljj9L.jpg 480w, /_astro/invoker-commands-visual.BdmT_e5s_PiYfL.jpg 680w, /_astro/invoker-commands-visual.BdmT_e5s_Z1LDSv1.jpg 800w, /_astro/invoker-commands-visual.BdmT_e5s_27pWtY.jpg 980w, /_astro/invoker-commands-visual.BdmT_e5s_1O5YGn.jpg 1024w, /_astro/invoker-commands-visual.BdmT_e5s_ZRhgts.jpg 1660w, /_astro/invoker-commands-visual.BdmT_e5s_Z1at8hH.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Open UI logo with text invokers below it" loading="eager" decoding="async" fetchpriority="auto" width="2000" height="1250" class="img-fluid"> </picture><h2 id="a-note-of-warning">A note of warning</h2> <p>What I’m writing about is still experimental. The invoker commands feature is being made available in multiple browsers, however, I tested my demos in <strong>Chrome Canary</strong>. If you want to play around with them you’ll have to enable the <code>Experimental Web Platform features</code> after going to <code>chrome://flags</code></p> <div class="alert alert-warning"><strong>Update 10 March 2025:</strong><ul> <li>This article was slightly updated with the latest syntax</li> <li>In Chrome 135 invoker commands will be available for dialogs, popovers and custom commands, <a href="https://developer.chrome.com/blog/command-and-commandfor" target="_blank">more info on the Chrome blog</a></li> <li>At current state, Chrome Canary needs to be launched with the following flags for the video, picker and stepper examples: <code>--enable-features=HTMLInvokeActionsV2,HTMLCommandActionsV2</code></li> <li>I plan to write some updated information on this later on, but most of this article still adds up for the future of invokers</li> </ul></div> <h3 id="why-care-about-an-experimental-feature">Why care about an experimental feature?</h3> <p>The reason you should be interested in this feature is that talking about it does help to move things forward. If we notice a big interest from people, it gets placed on top of the pile by the different working groups out there.</p> <p>I will be re-using some of the older demos I created and will add a new one as well. I’ll once again cover some of the basics while also adding a few differences compared to the previous explainer. If people land on this page, they don’t need to go back and forth on demos, so it seemes like the correct thing to do.</p> <h2 id="the-basics-creating-a-button-and-adding-a-command">The basics: Creating a button and adding a command</h2> <p>For the basic example, let’s take a look at how we could trigger a dialog by just using HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show-modal<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Trigger dialog<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dialog</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>This is my dialog<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dialog</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This opens the dialog element in the top-layer with the <code>::backdrop</code> pseudo-element attached. The only thing needed is the <code>commandfor</code> referencing an <code>id</code> and the <code>command</code> attribute specifying the <code>showModal</code> action.</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A dialog gets invoked by clicking a button"><source src="/_astro/invoker-dialog.CAwSHghd.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p><em><strong>One new thing compared to the old explainer</strong></em> we need to take into account is that when the <code>command</code> attribute is missing it will default to an invalid state. There will be some exploring to enable implicit <code>command</code> and/or <code>commandfor</code> values where the value can easily be inferred. (but probably after the initial shipping of the feature)</p> <p>Taking it one step further, what happens when you want to create your own little close button? Well, you could just add the following inside of your <code>&lt;dialog&gt;</code>:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>close<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Close <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Most of the time, when creating these kinds of experiences, we want a click on the <code>::backdrop</code> to trigger a close event of the dialog. For that, we can write a bit of extra JS. This should be fine for that:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> dialogs <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;dialog&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> dialogs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> <span class="token literal-property property">target</span><span class="token operator">:</span> dialog <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dialog<span class="token punctuation">.</span>nodeName <span class="token operator">===</span> <span class="token string">&quot;DIALOG&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dialog<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token string">&quot;dismiss&quot;</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><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <div class="alert alert-warning"><strong>Update 10 March 2025:</strong><ul><li>At the moment, Chrome recently gained support for the <code>closedby</code> attribute.</li><li><p><code>&lt;dialog closedby=“none”&gt;</code>: No user-triggered closing of dialogs at all.</p></li><li><p><code>&lt;dialog closedby=“closerequest”&gt;</code>: User pressing ESC (or another close trigger) closes the dialog.</p></li><li><p><code>&lt;dialog closedby=“any”&gt;</code>: User clicking outside the dialog, or pressing ESC, closes the dialog. This is similar to <code>popover=“auto”</code> behavior.</p></li></ul></div> <p>For the animations in and out of the top-layer we can rely on <code>@starting-style</code>. If you haven’t heard about that, I <a href="https://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style">created an article about that a little while ago</a> and the good news is that it’s part of <a href="https://web.dev/blog/interop-2024" target="_blank" rel="noreferrer noopener">Interop 2024</a>.</p> <p>It’s nice that this controls so easily, here is that CodePen demo (remember, tested in Chrome Canary)</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke dialog" src="https://codepen.io/utilitybend/embed/preview/GRLBVZX?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/GRLBVZX"> Invoke dialog</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="a-note-on-popovers">A note on popovers</h3> <p>The <code>commandfor</code> attribute can also be used to target popovers, this will be added with the initial release of the attribute. If an element has both a <code>popovertarget</code> and <code>commandfor</code> attribute, then <code>popovertarget</code> is ignored, or simply put: <code>commandfor</code> takes precedence.</p> <p>The <code>command</code> attribute for popovers will handle the following options:</p> <ul> <li><code>toggle-popover</code></li> <li><code>hide-popover</code></li> <li><code>show-popover</code></li> </ul> <p>These are the equivalent of the attribute <code>popovertargetaction</code> with options: <code>toggle</code>, <code>show</code>, and <code>hide</code>.</p> <p>In case you need to add other methods for closing, such as certain hardware buttons, do take a look at the <a href="https://html.spec.whatwg.org/multipage/interaction.html#the-closewatcher-interface" target="_blank" rel="noreferrer noopener">CloseWatcher API</a>. This API gets used under the hood for popovers and dialogs and is already available in <a href="https://www.youtube.com/watch?v=oqCsXbsuvM0" target="_blank" rel="noreferrer noopener">Chrome 120</a> and has a polyfill.</p> <p>What I love about these attributes is that we have so much that can be used straight out of the browser. Something that could only be achieved with custom scripting or libraries just a while ago. This is a great win for performance, but there is (potentially) a lot more to come… <strong>Let’s take a look at the potential next phases</strong> for invoker commands.</p> <h2 id="the-future-phase-of-invoker-commands">The future phase of invoker commands</h2> <p>Currently planned for a future phase in the invoker plan is to give the ability to invoke the <code>&lt;details&gt;</code> element and even form controls such as the <code>&lt;select&gt;</code> or certain <code>&lt;input&gt;</code> types.</p> <p>The basics are still the same, for a <code>&lt;details&gt;</code> element we could add the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-details<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>toggle<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> This will toggle the details <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-details<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span>This is a summary<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> This is the content <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>With <code>command</code> having the following options:</p> <ul> <li><code>toggle</code></li> <li><code>open</code></li> <li><code>close</code></li> </ul> <p>Details were originally added to phase 1, but have been delayed. Let’s take a closer look at the input elements because those could potentially be a lot of fun.</p> <h3 id="showing-pickers-with-the-command-showpicker">Showing pickers with the command showPicker()</h3> <p>Imagine you want to create an <code>&lt;input type=&quot;date&quot; /&gt;</code> with a custom icon, It would look something like this:</p> <picture> <source srcset="/_astro/date-image.H92j094F_ZBx48b.avif 320w, /_astro/date-image.H92j094F_nVFkl.avif 480w, /_astro/date-image.H92j094F_tE2wM.avif 800w, /_astro/date-image.H92j094F_aa4Lm.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/date-image.H92j094F_ZqVpUH.webp 320w, /_astro/date-image.H92j094F_yxjwO.webp 480w, /_astro/date-image.H92j094F_EfFJg.webp 800w, /_astro/date-image.H92j094F_kKHXP.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/date-image.H92j094F_Z1RL0Fv.png" srcset="/_astro/date-image.H92j094F_1VPrrr.png 320w, /_astro/date-image.H92j094F_Z27QVSX.png 480w, /_astro/date-image.H92j094F_Z229zGw.png 800w, /_astro/date-image.H92j094F_Z2lDxrW.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A custom styled datepicker with a button that opens the calendar widget" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>Now unless you do some hacks for the icon to be clickable (I’ve done some before), it could be nice to have this as an actual button just by using HTML and CSS. In that case, this could be our markup:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>date<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Pick a date<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>input-group<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>date<span class="token punctuation">&quot;</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">&quot;</span>date<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>date<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show-picker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- add an icon here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And by just adding a bit of styling…</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Remove the default date calendar picker */</span> <span class="token selector">[type=&quot;date&quot;]</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::-webkit-calendar-picker-indicator</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> <span class="token comment">/* Style the input group with invoker */</span> <span class="token selector">.input-group</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">[type=&quot;date&quot;]</span> <span class="token punctuation">{</span> <span class="token comment">/* Presentational styles here */</span> <span class="token punctuation">}</span> <span class="token selector">[commandfor]</span> <span class="token punctuation">{</span> <span class="token comment">/* Presentational styles here */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This could be a result:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A datepickers picker gets invoked by clicking a button"><source src="/_astro/invoke-date.BTvexFLq.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke date" src="https://codepen.io/utilitybend/embed/preview/YzMJOov?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/YzMJOov"> Invoke date</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>(This demo does not work well inside the CodePen iframe, view it <a href="https://cdpn.io/pen/debug/YzMJOov" target="_blank" rel="noreferrer noopener">in debug mode</a>)</p> <p>The same technique could be used to trigger a <code>&lt;datalist&gt;</code> with a separate button. Note that we need to trigger the <code>&lt;input&gt;</code> element’s <code>id</code>, not the one from the datalist:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>food<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>What would you like?<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>input-group<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">list</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>foods<span class="token punctuation">&quot;</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">&quot;</span>browser<span class="token punctuation">&quot;</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">&quot;</span>food<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>food<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show-picker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- icon --&gt;</span> Open input picker <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>datalist</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>foods<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>🍙 Onigiri<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🍜 Ramen<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🍣 Sushi<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🥟 Dimsum<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>datalist</span><span class="token punctuation">&gt;</span></span> </code></pre> <video width="320" height="240" controls="" preload="metadata" aria-label="A datepickers picker gets invoked by clicking a button"><source src="/_astro/invoke-datalist.D638RPYk.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke Datalist" src="https://codepen.io/utilitybend/embed/preview/KKYryKm?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/KKYryKm"> Invoke Datalist</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Isn’t it great how natural this feels? The CSS also seems less hacky this way instead of doing some workaround. Giving us the option of using the browser’s built-in date or datalist option picker with a custom button. (That’s just lovely…)</p> <p>This <code>showPicker()</code> action can be used on a numerous of elements: <code>&lt;input type=&quot;color&quot; /&gt;</code> or <code>&lt;select&gt;</code> elements, or even <code>&lt;input type=&quot;file&quot; /&gt;</code>:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Upload some files?<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>uploadzone<span class="token punctuation">&quot;</span></span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>showPicker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Click this huge square to upload<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>file<span class="token punctuation">&quot;</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">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> </code></pre> <p>Here is a demo of that:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A file picker gets invoked by clicking a button"><source src="/_astro/invoke-file.2IdZJ64v.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke file" src="https://codepen.io/utilitybend/embed/preview/PogyZwe?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/PogyZwe"> Invoke file</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Truth be told, you could probably create this effect with the label itself, but this being an actual <code>&lt;button&gt;</code> element could be a handy tool.</p> <h3 id="number-inputs-with-custom-steppers">Number inputs with custom steppers</h3> <p>A feature that would be welcome for people who create a lot of webshops ( I am such “people” ) is the ability to add custom stepper buttons on an <code>&lt;input type=&quot;number&quot; /&gt;</code>. There is a possibility to do this with invoker commands and this is how it works:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>counter<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>step-down<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>-<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>number<span class="token punctuation">&quot;</span></span> <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</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">&quot;</span>num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>num<span class="token punctuation">&quot;</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">&quot;</span>btn<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>step-up<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>+<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Using the <code>stepDown</code> and <code>stepUp</code> events for the <code>command</code>, we can control the number of the input type. This takes into account any of the extra attributes on the input as well such as <code>min</code>, <code>max</code>, and <code>step</code>.</p> <p>Using the HTML above and some extra styling this is something I created with it:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A number input field gets increased an decreased in steps of 10 with a custom button"><source src="/_astro/invoke-number.D3pHKHfP.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke number" src="https://codepen.io/utilitybend/embed/preview/abxKpdN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/abxKpdN"> Invoke number</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>While still a big “if” when speaking about this feature (because, well.. Experimental and such), I love the idea of implementing this with invoker commands. It seems that this invoker idea is fantastic when it comes to consistency, and I can only applaud that.</p> <p>There are a few caveats at the moment of writing. For the moment a <code>&quot;change&quot;</code> event doesn’t trigger when clicking our stepper buttons, this is something that has been discussed and is <a href="https://github.com/openui/open-ui/issues/1033" target="_blank" rel="noreferrer noopener">still in the works</a>.</p> <p>While we’re not done yet with all the things you can do with invoker commands, let’s take a little detour as events just came to mention:</p> <h2 id="custom-commands">Custom commands</h2> <p>Invoker commands will dispatch events on the Invokee element. Using a double dash as the start for the <code>command</code> allows for custom JavaScript to be triggered without having to wire up manual event handlers to the button. By listening to the <code>command</code> event listener we can attach our action to it:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>counter-button<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>my-counter<span class="token punctuation">&quot;</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">&quot;</span>number<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-counter<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--add-num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>10<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>+10<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-counter<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--add-num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>50<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>+50<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-counter<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>--add-num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>100<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>+100<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">const</span> counter <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">&quot;my-counter&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> counter<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;command&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>command <span class="token operator">===</span> <span class="token string">&quot;--add-num&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> counter<span class="token punctuation">.</span>value <span class="token operator">=</span> <span class="token function">Number</span><span class="token punctuation">(</span>counter<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">Number</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>source<span class="token punctuation">.</span>value<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> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> </code></pre> <video width="320" height="240" controls="" preload="metadata" aria-label="An input with 3 buttons, increasing the value of the input by 10, 50 or 100. clicking it does so."><source src="/_astro/invoke-multibutton.BcKdF84s.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>There are more examples to be found at the <a href="https://open-ui.org/components/invokers.explainer/" target="_blank" rel="noreferrer noopener">Open UI explainer</a></p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoker commands with custom action" src="https://codepen.io/utilitybend/embed/preview/zYVxXEZ/3e515af26fa32c736649fe0f016e1472?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/zYVxXEZ/3e515af26fa32c736649fe0f016e1472"> Invoker commands with custom action</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="custom-video-and-audio-controls-with-command">Custom video and audio controls with command</h2> <p>I’ve been showing some short demos of this while presenting about the future of UI and this is one of those things where I notice people gasping and getting a smile on their faces. Seems like people really had some struggles when it comes to custom controls for video and audio. Well, I hear you and Invoker actions might just make all of these things a bit more easy.</p> <p>This is how we could create a custom play button:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>video</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>custom-video<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>video</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">commandfor</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>custom-video<span class="token punctuation">&quot;</span></span> <span class="token attr-name">command</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>play<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Play <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Keeping the consistency going (love this!), we can create a <code>command=&quot;play&quot;</code> attribute and this will fire that play event for the video. This works with a whole bunch of events such as:</p> <ul> <li><code>play</code></li> <li><code>play-pause</code> (toggle between play and pause)</li> <li><code>pause</code></li> <li><code>toggle-muted</code></li> <li><code>toggle-fullscreen</code></li> <li><code>request-fullscreen</code></li> <li><code>close-fullscreen</code></li> </ul> <p>A bit of styling and you could create something like this:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A video has custom controls to play and pause, clicking it does make the video play and pause"><source src="/_astro/invoke-video.B9BL9-TQ.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke video - Would be cool to have :playing pseudo-class - cross browser for this" src="https://codepen.io/utilitybend/embed/preview/eYoyGoW?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/eYoyGoW"> Invoke command video - Would be cool to have :playing pseudo-class - cross browser for this</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>The only bit of JavaScript needed for this example is to add a class when the video is playing. This would not have been necessary if the <code>:playing</code> pseudo-class was supported everywhere, but unfortunately that isn’t the case at the moment.</p> <p>In case you were wondering, the same kinds of things can be achieved for audio as well.</p> <p>For more goodies and information about invoker commands, check out the following links:</p> <ul> <li> <a href="https://open-ui.org/components/invokers.explainer/" target="_blank" rel="noreferrer noopener">The Open UI invoker commands explainer</a> </li> <li> <a href="https://github.com/keithamus/invokers-polyfill" target="_blank" rel="noreferrer noopener">Invoker comands polyfill by Keith Cirkel</a> </li> <li> <a href="https://github.com/whatwg/html/pull/9841" target="_blank" rel="noreferrer noopener">The HTML PR for phase 1</a> </li> </ul> <h2 id="only-the-tip-of-the-iceberg">Only the tip of the iceberg</h2> <p>I am only scratching the surface here, but if you want to read the full list of built-in commands being suggested, do <a href="https://open-ui.org/components/invokers.explainer/#defaults" target="_blank" rel="noreferrer noopener">check out the full list here</a>.</p> <p>As a conclusion to this update, I know that these things might change once again, but I do feel it’s important to talk about them. I feel this feature leads to more consistency and readability in code, and I’m a sucker for that sort of thing.</p> <p>It’s more than a little quality-of-life update to HTML, but even if you think of it in that regard, imagine how many tedious tasks will get removed on a day-to-day basis.</p> <p>Open UI is a group effort but once again, I do want to give a shoutout to <a href="https://www.keithcirkel.co.uk/" target="_blank" rel="noreferrer noopener">Keith Cirkel</a> and <a href="https://lukewarlow.dev/" target="_blank" rel="noreferrer noopener">Luke Warlow</a>, who really have put a lot of effort into this feature and are continuing to do so. After this explainer update, the experimental implementations in browsers are already being handled for an update as well, absolutely legendary.</p> <p>Do remember that all of this is a work in progress. But if you like this and want to support this, create demos, provide feedback and make some noise.</p> <p>Note that some of my examples in this article might not be the perfect accessible examples at the moment. Keep in mind, that the group is working on improving accessibility, it is a topic that is highly rated.</p> <p>I do hope that by sharing these features people go check them out and give them a spin. I also hope that by writing this article and speaking about this, I can project a bit of my enthusiasm to others. Always feel free to tag me if you create something with them, I’d like to create a little collection on CodePen as well.</p>Brecht De RuyteThis was CSS Day 2024 - the 10th editionhttps://utilitybend.com/blog/this-was-css-day-2024-the-10th-edition/https://utilitybend.com/blog/this-was-css-day-2024-the-10th-edition/Another year, another CSS Day. I’m always happy to return to Amsterdam for this occasion, It’s one of the things I look forward to every year. So naturally, I’m happy that iO still allowed me to go there with my training budget, and the least I could do, is write a summary of this event on the tech_hub.Thu, 27 Jun 2024 00:00:00 GMT<picture> <source srcset="/_astro/io.Dg3cHcwk_Z25b8NH.avif 375w, /_astro/io.Dg3cHcwk_tOkKt.avif 480w, /_astro/io.Dg3cHcwk_Z5wM08.avif 680w, /_astro/io.Dg3cHcwk_todc8.avif 800w, /_astro/io.Dg3cHcwk_ZAfldy.avif 980w, /_astro/io.Dg3cHcwk_ZmaGMW.avif 1024w, /_astro/io.Dg3cHcwk_20jHNz.avif 1660w, /_astro/io.Dg3cHcwk_1Wjfzb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/io.Dg3cHcwk_Z1Ab5it.webp 375w, /_astro/io.Dg3cHcwk_XOogH.webp 480w, /_astro/io.Dg3cHcwk_osgv6.webp 680w, /_astro/io.Dg3cHcwk_XogHm.webp 800w, /_astro/io.Dg3cHcwk_Z6fhHk.webp 980w, /_astro/io.Dg3cHcwk_ZmJlnD.webp 1024w, /_astro/io.Dg3cHcwk_1YK4dS.webp 1660w, /_astro/io.Dg3cHcwk_1EH8qP.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/io.Dg3cHcwk_VsBpc.jpg" srcset="/_astro/io.Dg3cHcwk_16WAjf.jpg 375w, /_astro/io.Dg3cHcwk_Z1oe3Uv.jpg 480w, /_astro/io.Dg3cHcwk_Z1XAbG7.jpg 680w, /_astro/io.Dg3cHcwk_Z1oEbtQ.jpg 800w, /_astro/io.Dg3cHcwk_Z2tiJTx.jpg 980w, /_astro/io.Dg3cHcwk_2bttFL.jpg 1024w, /_astro/io.Dg3cHcwk_ZwdevD.jpg 1660w, /_astro/io.Dg3cHcwk_Zirdpp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="iO tech_hub" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://techhub.iodigital.com/articles/this-was-css-day-2024-the-10th-edition" target="_blank">Read the article at iO tech_hub</a></div>Brecht De RuyteLet’s hang! An intro to CSS Anchor Positioning with basic exampleshttps://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples/https://utilitybend.com/blog/lets-hang-an-intro-to-css-anchor-positioning-with-basic-examples/The CSS anchoring API is something that I’ve been following for at least a year now, and I’m happy to see that the first level is fully specced. Even though there are still some shortcomings that I’ll cover in this article as well, the available things look pretty solid and will help us a lot in creating some complex layout structures. Besides that, they are very much needed for the future of the HTML Popover API and stylable selects.Mon, 03 Jun 2024 00:00:00 GMT<picture> <source srcset="/_astro/anchoring-api-visual.DmqnoBQE_1h6qKc.avif 375w, /_astro/anchoring-api-visual.DmqnoBQE_Z1N060M.avif 480w, /_astro/anchoring-api-visual.DmqnoBQE_18g8ay.avif 680w, /_astro/anchoring-api-visual.DmqnoBQE_1mstdL.avif 800w, /_astro/anchoring-api-visual.DmqnoBQE_mmP93.avif 980w, /_astro/anchoring-api-visual.DmqnoBQE_Z2wsWU2.avif 1024w, /_astro/anchoring-api-visual.DmqnoBQE_1maBgY.avif 1660w, /_astro/anchoring-api-visual.DmqnoBQE_Z18m3kJ.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/anchoring-api-visual.DmqnoBQE_ZFllET.webp 375w, /_astro/anchoring-api-visual.DmqnoBQE_1jJfn3.webp 480w, /_astro/anchoring-api-visual.DmqnoBQE_ZObEfx.webp 680w, /_astro/anchoring-api-visual.DmqnoBQE_ZzYjck.webp 800w, /_astro/anchoring-api-visual.DmqnoBQE_Z1A4Wh3.webp 980w, /_astro/anchoring-api-visual.DmqnoBQE_Z2dcPlR.webp 1024w, /_astro/anchoring-api-visual.DmqnoBQE_1FqIP9.webp 1660w, /_astro/anchoring-api-visual.DmqnoBQE_Z1vyuBw.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/anchoring-api-visual.DmqnoBQE_Z11NQDF.jpg" srcset="/_astro/anchoring-api-visual.DmqnoBQE_Z1Ml3dV.jpg 375w, /_astro/anchoring-api-visual.DmqnoBQE_cJxO1.jpg 480w, /_astro/anchoring-api-visual.DmqnoBQE_Z1VblNz.jpg 680w, /_astro/anchoring-api-visual.DmqnoBQE_Z1GY0Km.jpg 800w, /_astro/anchoring-api-visual.DmqnoBQE_2n7tXQ.jpg 980w, /_astro/anchoring-api-visual.DmqnoBQE_Z1aErUl.jpg 1024w, /_astro/anchoring-api-visual.DmqnoBQE_Z2md1xg.jpg 1660w, /_astro/anchoring-api-visual.DmqnoBQE_gcwW8.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A boats anchor agains a futuristic background with rectangle patterns" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>Before I get into the details of why some things are still missing in the Anchoring API, I want to get on the hype train. Because, no matter the small issues (or, let’s call them missed opportunities?) I spotted, this is something I wanted for a long time and it’s pretty awesome. The anchoring API has been inside of my <em>future of UI</em> talk for quite some time and in just a moments time, I had to change this from experimental code to specced code, and soon after as available in Chrome. Yes, you read that right, what we’ll be covering here is already available in Chrome 125 (at the moment of writing, the latest version).</p> <p>For this article, we won’t be creating the most fancy examples out there, this is a (complete) basic introduction with clear examples. After reading this, you sould have a solid basic understanding on how this anchoring system works.</p> <div class="alert alert-warning"><p><strong>Note</strong>: This article and examples within have been updated on the 9th of August 2024 with the new spec. For just once, I decided to update this article. <a href="https://utilitybend.com/blog/a-spec-update-on-css-anchor-positioning">You can find information about the updates here.</a></p></div> <h2 id="anchoring-api---two-specs-merged-into-one">Anchoring API - two specs merged into one?</h2> <p>There are currently two major ways how you can get anchoring to happen. Without going too much into the history, this is the short version of why that is: There was an experimental implementation with the <code>anchor()</code> function and <code>@position-fallback</code> (not position-try, we’ll get there) when a new idea was launched by Webkit with an alternative solution using more of a grid-like system. From what I can tell, some frustrations happened and this certainly slowed things down, but this has some benefits:</p> <ul> <li>We get to have the best of both worlds, both ideas were great</li> <li>The original <code>anchor()</code> idea is probably more powerful and versatile, while the grid idea gives a more easy and quick everyday solution</li> </ul> <p>Besides that, there are a few downsides:</p> <ul> <li>This is harder to teach to people as there are multiple ways in using anchors</li> <li>Both methods don’t have a fallback for arrow indications on tooltips and I wanted both methods to have it on first spec… (more on that later)</li> </ul> <p>This is why, in this article, I’d like to focus on all the basics with simple demos, but I’ll be adding some links to other sources and tools as well. Let’s start off with taking a look at the basics of these two methods:</p> <h2 id="the-basics-of-anchoring-with-css">The basics of anchoring with CSS</h2> <p>As we’re going for some solid understanding, let’s get the tutorial started by creating 2 <code>&lt;div&gt;</code> elements. For easy reference for the rest of this article, let’s name these with a class <code>.anchor</code> and <code>.anchored</code>.</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>anchor<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>anchored<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Inside of the <code>.anchor</code> I added an anchor icon for reference, this is purely optional. Let’s add some basic styling to differentiate the two:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">body</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-direction</span><span class="token punctuation">:</span> column<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 property">align-items</span><span class="token punctuation">:</span> center<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">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 20vmin<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.anchor</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> goldenrod<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 selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> olive<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> </code></pre> <p>The elements are placed in the center of our viewport by giving our body <code>display: flex;</code> We gave both of the <code>div</code> elements a relative height of <code>20vmin</code> (20% of the viewport’s smallest side). We also added a different <code>aspect-ratio</code> to each of the items, this will come in handy visually later on.</p> <p>Besides the extra background added to the <code>&lt;body&gt;</code> for prettifying the demo a bit, this should pretty much be the result:</p> <picture> <source srcset="/_astro/default.M6P1P5j-_bOX6x.avif 320w, /_astro/default.M6P1P5j-_Z12LUe8.avif 480w, /_astro/default.M6P1P5j-_Z26beq1.avif 800w, /_astro/default.M6P1P5j-_Z17NPhw.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/default.M6P1P5j-_ZfA0Mg.webp 320w, /_astro/default.M6P1P5j-_Z1ucT7V.webp 480w, /_astro/default.M6P1P5j-_2wzUu7.webp 800w, /_astro/default.M6P1P5j-_Z1zeObk.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/default.M6P1P5j-_o9ORq.png" srcset="/_astro/default.M6P1P5j-_Z1s25BR.png 320w, /_astro/default.M6P1P5j-_2nx9Qo.png 480w, /_astro/default.M6P1P5j-_1k8PEv.png 800w, /_astro/default.M6P1P5j-_2iveN0.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Two elements below each other in the center of the screen, one of them is a square in yellow with an anchor icon on it, the other has a 16/9 aspect ratio in green below it" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <h3 id="method-1-using-anchor">Method 1: using anchor()</h3> <p>The first method is the closest to the original proposal back when I first heard about this API. Let’s introduce a few new properties, <code>anchor-name</code> and <code>position-anchor</code>.</p> <p>First, we’ll need to define our anchor, this is where <code>anchor-name</code> comes in. Let’s add the following to our <code>.anchor</code></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchor</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --anchortome<span class="token punctuation">;</span> <span class="token comment">/* previous code */</span> <span class="token punctuation">}</span> </code></pre> <p>The anchor name should be prefixed with <code>--</code> , just as you would do with CSS variables but besides that, you can give it any name you want.</p> <p>Next up, let’s add the <code>position-anchor</code> to our <code>.anchored</code> item, this will create the missing link (anchor) between the elements:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<span class="token punctuation">;</span> <span class="token comment">/* previous code */</span> <span class="token punctuation">}</span> </code></pre> <p>We don’t see much happening yet, that’s because we still need to position our items. To do this, we will absolutely position our <code>.anchored</code> element, and we will introduce the <code>anchor()</code> function to position it, this is the full updated code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<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> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* previous code */</span> <span class="token punctuation">}</span> </code></pre> <p>What we’re saying here is:</p> <blockquote> <p>Place the top of this div to the linked anchor’s center, and place the left of this div to the right of the linked anchor.</p> </blockquote> <p>This results in the following:</p> <picture> <source srcset="/_astro/anchor-anchored.Bwy15MGC_15JNkW.avif 320w, /_astro/anchor-anchored.Bwy15MGC_Zk7wuM.avif 480w, /_astro/anchor-anchored.Bwy15MGC_Z1K1JHN.avif 800w, /_astro/anchor-anchored.Bwy15MGC_Z1tDY6D.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/anchor-anchored.Bwy15MGC_Z1t0qbJ.webp 320w, /_astro/anchor-anchored.Bwy15MGC_2bjmLs.webp 480w, /_astro/anchor-anchored.Bwy15MGC_Kp9yr.webp 800w, /_astro/anchor-anchored.Bwy15MGC_11LUaB.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/anchor-anchored.Bwy15MGC_ZaKRfr.png" srcset="/_astro/anchor-anchored.Bwy15MGC_2nxGDO.png 320w, /_astro/anchor-anchored.Bwy15MGC_WFlN5.png 480w, /_astro/anchor-anchored.Bwy15MGC_ZsdQoV.png 800w, /_astro/anchor-anchored.Bwy15MGC_ZbQ5ML.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Two elements next each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is tethered to the yellow one on the right hand side withe it's stop starting at the center of the one with the anchor" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>But there are more options available, we could use a <code>&lt;percentage&gt;</code> unit for our top, instead of using <code>anchor(center)</code> as the value of our <code>top</code> property, let’s add a percentage of <code>anchor(20%)</code>. This would result in the following:</p> <picture> <source srcset="/_astro/anchor-percentage.D7hwdYuh_1kMJSJ.avif 320w, /_astro/anchor-percentage.D7hwdYuh_18tRRc.avif 480w, /_astro/anchor-percentage.D7hwdYuh_Zejhfe.avif 800w, /_astro/anchor-percentage.D7hwdYuh_Z2uMBuW.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/anchor-percentage.D7hwdYuh_Z1T583J.webp 320w, /_astro/anchor-percentage.D7hwdYuh_Z26o05h.webp 480w, /_astro/anchor-percentage.D7hwdYuh_1AYXBe.webp 800w, /_astro/anchor-percentage.D7hwdYuh_ZEtlDu.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/anchor-percentage.D7hwdYuh_ZGS2Y9.png" srcset="/_astro/anchor-percentage.D7hwdYuh_1Yr4pH.png 320w, /_astro/anchor-percentage.D7hwdYuh_1M8coa.png 480w, /_astro/anchor-percentage.D7hwdYuh_pk2gJ.png 800w, /_astro/anchor-percentage.D7hwdYuh_Z1Q9hXY.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Two elements next each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is tethered to the yellow one on the right hand side withe it's stop starting at 20% height of the one with the anchor" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>Besides percentages, you could also use <strong>logical properties</strong> for the alignment, for example:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<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-block-start</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">inset-inline-start</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>end<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This way, you can anchor based on <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode" target="_blank" rel="noopener noreferrer">the writing mode</a>.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/utilitybend/embed/preview/WNBpjrB?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/WNBpjrB"> Basic anchor example</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="method-2-using-the-position-area-syntax">Method 2: Using the position-area syntax</h3> <p>Let’s remove everything we did in the first method and use a different approach to how we can anchor something.</p> <p>On our <code>.anchor</code>, we’ll once again add the name, same idea as before:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchor</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --anchortome<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Next up we’ll link our anchor again and add a new property named <code>position-area</code> to our <code>.anchored</code> item:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<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">position-area</span><span class="token punctuation">:</span> top span-all<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This will give you the following result (explained below image)</p> <picture> <source srcset="/_astro/grid-top-span-all.6TA8Y5ZZ_248cYO.avif 320w, /_astro/grid-top-span-all.6TA8Y5ZZ_1QOkXh.avif 480w, /_astro/grid-top-span-all.6TA8Y5ZZ_u1aPQ.avif 800w, /_astro/grid-top-span-all.6TA8Y5ZZ_Z1Ls9oR.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/grid-top-span-all.6TA8Y5ZZ_Z1aJEWE.webp 320w, /_astro/grid-top-span-all.6TA8Y5ZZ_Z1n3wYc.webp 480w, /_astro/grid-top-span-all.6TA8Y5ZZ_2kkqHj.webp 800w, /_astro/grid-top-span-all.6TA8Y5ZZ_3Q6rA.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/grid-top-span-all.6TA8Y5ZZ_1rp6V.png" srcset="/_astro/grid-top-span-all.6TA8Y5ZZ_Z2mpBi9.png 320w, /_astro/grid-top-span-all.6TA8Y5ZZ_2vsEuf.png 480w, /_astro/grid-top-span-all.6TA8Y5ZZ_18EumO.png 800w, /_astro/grid-top-span-all.6TA8Y5ZZ_Z17NORT.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Two elements above each other in the center of the screen, one of them is a square in yellow centered on the screen with an anchor icon on it, the other has a 16/9 aspect ratio in green and is sitting on top of the yellow one, center aligned" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>What happened here? The <code>position-area</code> method draws a 3x3 grid around our <code>.anchor</code> behind the scenes, with the anchor item being in the middle column and row, something like this:</p> <picture> <source srcset="/_astro/anchor-lines.B_BQMmMT_WFEfr.avif 320w, /_astro/anchor-lines.B_BQMmMT_SlBcI.avif 480w, /_astro/anchor-lines.B_BQMmMT_19UANQ.avif 800w, /_astro/anchor-lines.B_BQMmMT_ZnSCS.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/anchor-lines.B_BQMmMT_1ks4C8.webp 320w, /_astro/anchor-lines.B_BQMmMT_1g81zp.webp 480w, /_astro/anchor-lines.B_BQMmMT_1wH1bx.webp 800w, /_astro/anchor-lines.B_BQMmMT_mnvIN.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/anchor-lines.B_BQMmMT_2umqX2.png" srcset="/_astro/anchor-lines.B_BQMmMT_Y4iaC.png 320w, /_astro/anchor-lines.B_BQMmMT_TJf7T.png 480w, /_astro/anchor-lines.B_BQMmMT_1bjeJ2.png 800w, /_astro/anchor-lines.B_BQMmMT_YJhi.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A yellow square centered on the screen with an anchor icon on it, it is centered in a 3 by 3 grid. The grid is drawn with green lines." loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>In our example <code>position-area</code> is set to <code>top</code>, making our item sit at the top row of our anchor grid. The second option is set to <code>span-all</code>, telling the browser that it’s ok the span over the other two columns of the 3x3 grid.</p> <p>You can span over both axes, part of an axis, or none at all, you can even use logical properties. I would get lost in getting to the foundations of this article so I’ll just refer to this <a href="https://anchor-tool.com/" target="_blank" rel="noreferrer noopener">handy anchor-tool created by Una Kravets</a>, and suggest you play around with it to see the potential. To see all the spanning options out there, I’d like to refer to the <a href="https://www.w3.org/TR/css-anchor-position-1/#resolving-spans" target="_blank" rel="noreferrer noopener">anchoring spec</a> as well</p> <h3 id="note-on-the-basic-anchoring-methods">Note on the basic anchoring methods</h3> <p>Using the <code>position-area</code> syntax will likely be the most commonly used. I’m talking about popovers, tooltips, fly-out menus, etc…</p> <p>Although two methods make it harder to explain to people, I am happy that both of these methods came to exist simultaneously as the first method gives us much more flexibility for the times we need it. The power of these anchoring methods goes far beyond these basic demos, but they do show the capability,</p> <p>Let’s take it one step further because when anchoring an item, we get some new things to play around with that give us the possibility to provide a fallback position to our anchored items based on the viewport.</p> <h2 id="using-the-css-anchoring-basic-fallback-position-try-fallbacks">Using the CSS anchoring basic fallback: position-try-fallbacks</h2> <p>More often than not, we want our anchoring to be visible no matter where the edge of our screen is. This happens a lot in software menus or tooltips for example. Some keywords were added for this to happen, a lovely addition. These keywords get referenced as <em>“try-tactics”</em>:</p> <ul> <li><code>flip-block</code></li> <li><code>flip-inline</code></li> <li><code>flip-start</code></li> </ul> <p>It’s great that this uses logical naming from the start… Let’s go back tot he first example, but this time around, we’ll give it some <code>position-try-fallbacks</code>. We’ll make the first fallback a block flip, followed by an inline flip, if that still doesn’t work, it should flip both axes:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> olive<span class="token punctuation">;</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<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> <span class="token function">anchor</span><span class="token punctuation">(</span>center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>right<span class="token punctuation">)</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 property">position-try-fallbacks</span><span class="token punctuation">:</span> flip-inline<span class="token punctuation">,</span> flip-block<span class="token punctuation">,</span> flip-block flip-inline<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>If the anchor now gets placed to the bottom right, this is the result:</p> <picture> <source srcset="/_astro/bottom-right.C3GA6coA_2l48Nc.avif 320w, /_astro/bottom-right.C3GA6coA_Z2vQdXu.avif 480w, /_astro/bottom-right.C3GA6coA_ZEsdpx.avif 800w, /_astro/bottom-right.C3GA6coA_Zwyw0q.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/bottom-right.C3GA6coA_Z2mlzD3.webp 320w, /_astro/bottom-right.C3GA6coA_Z294NAN.webp 480w, /_astro/bottom-right.C3GA6coA_ZhFN2Q.webp 800w, /_astro/bottom-right.C3GA6coA_Z9M6CJ.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/bottom-right.C3GA6coA_Z2uEpa0.png" srcset="/_astro/bottom-right.C3GA6coA_2mrLIn.png 320w, /_astro/bottom-right.C3GA6coA_Z2usA3j.png 480w, /_astro/bottom-right.C3GA6coA_ZD4zum.png 800w, /_astro/bottom-right.C3GA6coA_ZvaS5f.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A yellow square is at the bottom right of the screen with an anchor icon on it, There is a green rectangle attachted to it on the left hand side with the bottom of that rectangle at the center of the yellow square" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p><em>That’s flippin’ awesome!</em></p> <p>Here is a full demo to play around with ( I added some controls):</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Anchoring flipping with position-try-fallbacks" src="https://codepen.io/utilitybend/embed/preview/mdYORaM?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/mdYORaM"> Anchoring flipping with position-try-fallbacks</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>(<strong>note</strong>: you could easily do this with the <code>position-area</code> example as well)</p> <h2 id="creating-our-own-position-try-fallbacks-with-position-try">Creating our own position-try-fallbacks with @position-try</h2> <p>What if we needed something more tailered to our needs and those basic try-tactics just aren’t enough? For those occasions we have the possibility to provide some <code>position-try-fallbacks</code> ourselves. Let’s create a basic example with a custom fallback. We’ll use the same anchor as before but this time add some text inside the anchored element:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>anchor<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- icon --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>anchored<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>In this example, the anchor has a custom position-try<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>What we want to achieve is to have our <code>.anchored</code> item to be the exact height of our <code>.anchor</code>, we can manage this by setting the <code>top</code> and <code>bottom</code> of the anchored item to <code>0</code>. The CSS of our anchor stays the same, but we need to update our <code>.anchored</code> element, this is the fully updated code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</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">padding</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --anchortome<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">bottom</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">align-self</span><span class="token punctuation">:</span> stretch<span class="token punctuation">;</span> <span class="token property">position-try-fallbacks</span><span class="token punctuation">:</span> --hold-left<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> olive<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The value of the <code>position-try-fallbacks</code> property allows a custom name as a value. Same as with <code>anchor-name</code> it should have two dashes (<code>--</code>) to start. It can be combined with the basic try-tactics as well.</p> <p>We’re going to try and position our <code>.anchored</code> item completely left or right from our <code>.anchor</code> filling in the full height. For this, we only need one fallback, but you can add as many as you want separated by a comma. Do note that the order matters.</p> <p>Let’s create that custom try option with the <code>@position-try</code> rule:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@position-try</span> --hold-left</span> <span class="token punctuation">{</span> <span class="token property">top</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">bottom</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>bottom<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We just created our own little position-try-option, so cool that we can do this! Inside the <code>@position-try</code> rule, only the following properties are allowed:</p> <ul> <li>inset properties</li> <li>margin properties</li> <li>sizing properties</li> <li>self-alignment properties</li> <li>position-anchor</li> <li>position-area</li> </ul> <p>It offers a lot of possibilities, however, I still notice some issues with this from time to time, but then again, it’s still an early implementation. I have yet to play around with everything available here.</p> <p>This is the result:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Anchoring API: Using custom position-try-fallbacks with @position-try" src="https://codepen.io/utilitybend/embed/preview/rNgjrZo?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/rNgjrZo"> Anchoring API: Using custom position-try-fallbacks with @position-try</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="picking-what-matters-most-for-our-anchoring-options-with-position-try-order">Picking what matters most for our anchoring options with position-try-order</h2> <p>Having the ability to pick different fallbacks is awesome, what’s even more handy is to have a bit of control over how the fallbacking should occur. With <code>position-try-order</code> we can tell the browser what’s most important to us for the anchored item:</p> <ul> <li><code>normal</code>: the default</li> <li><code>most-width</code>: Chooses the fallback that provides the most width for the anchored item</li> <li><code>most-height</code>: Chooses the fallback that provides the most height for the anchored item</li> <li><code>most-inline-size</code>: Chooses the fallback that provides the biggest inline-size for the anchored item</li> <li><code>most-block-size</code>: Chooses the fallback that provides the biggest block-size for the anchored item</li> </ul> <p>With more CSS features rising, it’s always good to have the logical variant of widths and heights baked in from the start. The same goes for <code>position-try-order</code>. If you’re unfamiliar with logical properties, I recommend you get into those as soon as possible. There is a great <a href="https://web.dev/learn/css/logical-properties" target="_blank" rel="noreferrer noopener">episode of the CSS podcast about getting into logical property</a>.</p> <p>But why would we need this? As an example, I took the previous demo, and gave the anchor a left offset of <code>20vw</code> from the center.</p> <picture> <source srcset="/_astro/anchor-offset-option.BmHXzt_3_Y18ws.avif 320w, /_astro/anchor-offset-option.BmHXzt_3_Z2horJz.avif 480w, /_astro/anchor-offset-option.BmHXzt_3_Z2jc20f.avif 800w, /_astro/anchor-offset-option.BmHXzt_3_Z1S9Nms.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/anchor-offset-option.BmHXzt_3_K9G6o.webp 320w, /_astro/anchor-offset-option.BmHXzt_3_Z2vfTaD.webp 480w, /_astro/anchor-offset-option.BmHXzt_3_Z2x3tqj.webp 800w, /_astro/anchor-offset-option.BmHXzt_3_Z271fMw.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/anchor-offset-option.BmHXzt_3_22uchB.png" srcset="/_astro/anchor-offset-option.BmHXzt_3_3XVs1.png 320w, /_astro/anchor-offset-option.BmHXzt_3_1RKtYU.png 480w, /_astro/anchor-offset-option.BmHXzt_3_1PWTJf.png 800w, /_astro/anchor-offset-option.BmHXzt_3_2h08n2.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A yellow square is at the center of the page with a small offset to the right. A green rectangle is hanging on the right edge of the yellow square, filling the remaining space of the screen on the right hand side. The green rectangle has some lorem ipsum placeholder text in it." loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>Well, it still works… But it could be better:<br> <em>We have more room on the left of the anchor and it could make sense to place the anchored item over there.</em></p> <p>Since being anchored to the right is the default, it’s not doing that because it does have enough room to show it on the right of the anchor. So let’s update the anchored item and tell the browser to focus on getting our anchored item as wide as possible:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token comment">/* Same styles as before */</span> <span class="token property">position-try-fallbacks</span><span class="token punctuation">:</span> --hold-left<span class="token punctuation">;</span> <span class="token property">position-try-order</span><span class="token punctuation">:</span> most-width<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We can even write this a bit shorter with the shorthand. In that case, the order comes first and the options second:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.anchored</span> <span class="token punctuation">{</span> <span class="token property">position-try</span><span class="token punctuation">:</span> most-width --hold-left<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>And here is that final result, taking the <code>most-width</code> into account:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Anchoring API: Using position-try-order to get the most-width option" src="https://codepen.io/utilitybend/embed/preview/mdYWeeb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/mdYWeeb"> Anchoring API: Using position-try-order to get the most-width option</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="i-did-run-into-issues">I did run into issues…</h2> <p>With <code>position-try-fallbacks</code>, I have run into the situation from time to time that the browser doesn’t understand my intent. This makes sense as there are still some bugs open related to this property. When creating an omnidirectional popover I just couldn’t get it perfect.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Anchored popover" src="https://codepen.io/utilitybend/embed/preview/zYQKdNN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/zYQKdNN"> Anchored popover</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="future-things-to-look-out-for-in-anchoring">Future things to look out for in anchoring</h2> <p>There are a few things I’m looking forward to, some of them, I kinda wished would’ve shipped together with the things already covered in this article. But it is what it is…</p> <h3 id="anchor-scope">anchor-scope</h3> <p>One property that is currently not yet available and has been specced is the ability to scope anchor names. When a design pattern is re-used, anchor-scope can prevent naming clashes across identical components.</p> <p>When anchoring items, you need a unique name for now, and especially when creating tooltips, this could be a hassle. I played around with this before, setting an inline style to the buttons and popovers which set a variable with a unique name:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Anchoring API and Popover API with position-fallback" src="https://codepen.io/utilitybend/embed/preview/MWzgvzp?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/MWzgvzp"> Anchoring API and Popover API with position-fallback</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>But this could be removed in the future and be turned into something like this in CSS (If I read <a href="https://www.w3.org/TR/css-anchor-position-1/#anchor-scope" target="_blank" rel="noreferrer noopener">the spec</a> right) If you’re reading this after <code>anchor-scope</code> became available, I will leave the demo in here for historic value (how i had to do it and a remembering of how awesome it is to have the scope feature)</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --tooltip<span class="token punctuation">;</span> <span class="token property">anchor-scope</span><span class="token punctuation">:</span> --tooltip<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">[popover]</span> <span class="token punctuation">{</span> <span class="token property">position-anchor</span><span class="token punctuation">:</span> --tooltip<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Did you notice the previous CodePen with the arrow not showing in the correct position, well, that brings me to the following:</p> <h3 id="tether-pseudo-element-or-alternative-idea">::tether pseudo-element? Or alternative idea</h3> <p>There has been talk about a <code>::tether</code> pseudo-element in the <a href="https://github.com/w3c/csswg-drafts/issues/9271" target="_blank" rel="noopener noreferrer">CSSWG</a>, and other ideas were given as well. I’m not part of the CSSWG, but I’m really rooting for a solution for the arrows in combination with <code>position-try-fallbacks</code>. It’s probably something clients will ask to have as well…</p> <h3 id="devtools">DevTools!</h3> <p>Yes! Just yes! More debugging tools for DevTools such as showing which try-option is currently active would be lovely.</p> <h2 id="a-note-on-accessibility-and-feature-flagging">A note on accessibility and feature flagging</h2> <p>Do note that even though you hang items to each other with the anchoring API, this is only visual. Although it’s now perfectly possible to tether elements to each other that are at complete different locations in the DOM, it has no effect on the accessibility tree, be mindful of that. Since this is an early feature, it’s something that we might just try out from time to time as a progressive enhancement. This is how you can check for support in your CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">anchor-name</span><span class="token punctuation">:</span> --boat<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Add your anchor awesomeness */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="looking-forward-to-learning-more-at-css-day">Looking forward to learning more at CSS Day!</h2> <p>In conclusion, I can only say that I’m absolutely happy that this Anchoring API has been specced. Apart from some issues, it’s clear that this will be a total game changer for popovers, flyout menus, awesome navigation, etc. Maybe Chrome released it a bit too quickly? 🤔 Just an honest thought, but I’m sure they are on top of the open bugs related to this API.</p> <p>We’re just a few days away from <a href="https://cssday.nl/2024" target="_blank" rel="noreferrer noopener">CSS Day</a> and I’m sure there will be a lot of talk about Anchoring Position. <a href="https://tabatkins.com/" target="_blank" rel="noopener noreferrer">Tab Atkins</a> will be giving a talk about it, really looking forward to that one.</p> <p>We are getting spoiled with CSS, I was kind off expecting things to slow down a bit after the crazy two years we had, but that’s far from true. We’ve only started with Anchoring, scroll-driven animations, transition-behavior, and view transition and it seems we’ll soon be animating from height auto to a value as well. But that’s a story for another time.</p> <p>Almost time for a few days of <em>extreme CSS geeking</em> at CSS Day!</p> <p>Some more cool demos and resources about the anchoring API:</p> <ul> <li><a href="https://developer.chrome.com/blog/anchor-positioning-api" target="_blank" rel="noreferrer noopener">Introducing the CSS anchor positioning API</a> - by Una Kravets</li> <li><a href="https://kizu.dev/anchor-positioning-experiments/" target="_blank" rel="noreferrer noopener">Future CSS: Anchor Positioning</a> - by Roman Komarov</li> <li><a href="https://www.w3.org/TR/css-anchor-position-1" target="_blank" rel="noreferrer noopener">CSS Anchoring positioning spec</a> (really well written)</li> </ul>Brecht De RuyteModern CSS Layouts: You Might Not Need A Framework For Thathttps://utilitybend.com/blog/modern-css-layouts-no-framework-needed/https://utilitybend.com/blog/modern-css-layouts-no-framework-needed/It’s easy to get lost in a sea of CSS frameworks and libraries, each promising easier styling and smoother layouts. But amidst this abundance, the modern CSS features we have today offer simpler and more flexible approaches without the added dependencies or abstractions. In this article, I demonstrate four CSS utility classes (plus a bonus) using techniques that allow them to be used practically anywhere you need a particular layout — be it Grid or Flexbox — with configurable options.Wed, 22 May 2024 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2024/05/modern-css-layouts-no-framework-needed/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteInvoking elements by using only HTML: A first look at invokershttps://utilitybend.com/blog/invoking-elements-by-using-only-html-a-first-look-at-invokers/https://utilitybend.com/blog/invoking-elements-by-using-only-html-a-first-look-at-invokers/Wouldn’t it be cool if we could click on a button to open a modal with just HTML? How about those file input elements? Imagine that creating a custom play button for video controls would be an easy thing to do. How about custom counter buttons for a number input? This is where the idea of invokers comes in. It’s currently available behind a flag to play around with and it’s definitely one of the most exciting advances in HTML to look out for.Tue, 23 Apr 2024 00:00:00 GMT<picture> <source srcset="/_astro/invokers-visual.15xw0goh_Z106Iac.avif 375w, /_astro/invokers-visual.15xw0goh_Z1O2mOh.avif 480w, /_astro/invokers-visual.15xw0goh_Z1xIir1.avif 680w, /_astro/invokers-visual.15xw0goh_Z134EGy.avif 800w, /_astro/invokers-visual.15xw0goh_1mzOoa.avif 980w, /_astro/invokers-visual.15xw0goh_ZyeLA9.avif 1024w, /_astro/invokers-visual.15xw0goh_ZBcQf6.avif 1660w, /_astro/invokers-visual.15xw0goh_1po6JI.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/invokers-visual.15xw0goh_1vkb73.webp 375w, /_astro/invokers-visual.15xw0goh_GowrX.webp 480w, /_astro/invokers-visual.15xw0goh_WHAPe.webp 680w, /_astro/invokers-visual.15xw0goh_1smezG.webp 800w, /_astro/invokers-visual.15xw0goh_Z1cap8w.webp 980w, /_astro/invokers-visual.15xw0goh_18r8Ss.webp 1024w, /_astro/invokers-visual.15xw0goh_1e6oBm.webp 1660w, /_astro/invokers-visual.15xw0goh_Z1OtLcK.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/invokers-visual.15xw0goh_20x1Iw.jpg" srcset="/_astro/invokers-visual.15xw0goh_Zt98f5.jpg 375w, /_astro/invokers-visual.15xw0goh_Z1i4LTa.jpg 480w, /_astro/invokers-visual.15xw0goh_Z11KHvT.jpg 680w, /_astro/invokers-visual.15xw0goh_Zw74Lr.jpg 800w, /_astro/invokers-visual.15xw0goh_1Sxpjh.jpg 980w, /_astro/invokers-visual.15xw0goh_pV2sB.jpg 1024w, /_astro/invokers-visual.15xw0goh_ZwThfk.jpg 1660w, /_astro/invokers-visual.15xw0goh_1tGFJu.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Open UI logo with text invokers below it" loading="eager" decoding="async" fetchpriority="auto" width="2000" height="1250" class="img-fluid"> </picture><div class="alert alert-warning"><p><strong>Attention:</strong> This article presents information from an experimental phase. An update on this article has been written, also documenting the latest updates to the explainer and demos. The article can be found here: <a href="https://utilitybend.com/blog/an-update-on-invokers-invoker-commands-in-html">An update on invokers: Invoker commands in HTML</a>.</p><br><p>The article below is kept for historic purposes.</p></div> <p>Now that full browser support for popovers is right around the corner, it might be a good time to start talking about invokers. The starting point of this idea can be traced back to 2018 and started with a shortcoming of the dialog element. To trigger a dialog element as a modal, adding the backdrop functionality and all that goodness, we had to rely on JavaScript, there just wasn’t another way. An <a href="https://github.com/whatwg/html/issues/3567" target="_blank" rel="noreferrer noopener">issue was created</a> for that, but a lot of ideas and iterations later we might finally get to see a solution for this in the form of invokers. But invokers include much more than that, and that’s exactly what this article is about.</p> <p>The reason why I love writing about this feature is that I will be talking more about the possibilities instead of the syntax, because that’s the beauty of it: it’s quite a simple syntax. This also translates to a great developer experience - and yes - I’ll be saving the best for last.</p> <p><strong>A note of warning</strong><br> What I’m writing about is still experimental, but from what I read and heard about it, it does get some nice interest from different browser vendors. The reason I want to write about this is because I love where this is going, but that’s just from a developer’s point of view. ( <em>And because it gets discussed in the Open UI community group and that’s just an awesome group</em> )</p> <p>The invoker feature is being made available in multiple browsers, however, I tested my demos in <strong>Chrome Canary</strong>. If you want to play around with them you’ll have to enable the <code>Experimental Web Platform features</code> after going to <code>chrome://flags</code></p> <h2 id="the-basics-creating-a-button-and-applying-an-invoke-action">The basics: Creating a button and applying an invoke action</h2> <p>For the basic example, let’s take a look at how we could invoke a dialog by just using HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Trigger dialog<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dialog</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>This is my dialog<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dialog</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This opens the dialog element in the top-layer as a modal with the <code>::backdrop</code> pseudo-element attached. The only thing needed is an <code>invoketarget</code> that references the <code>id</code> of the dialog. Pretty sweet, right?</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A dialog gets invoked by clicking a button"><source src="/_astro/invoke-dialog.D6-rFrjR.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>There is a second attribute that we could call that allows us to attach a certain event to the invoker, this is the <code>invokeaction</code>. For our close button, we could add the following</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-modal<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>close<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Close <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>For dialogs, there are a few invoke actions we can apply:</p> <ul> <li><code>showModal</code></li> <li><code>Close</code></li> </ul> <p>Not providing an action will call <code>showModal()</code> when the dialog isn’t open, otherwise it will close and use the button value for <code>returnValue</code>.</p> <p>Most of the time, when creating these kinds of experiences, we want a click on the <code>::backdrop</code> to trigger a close event of the dialog. For that, we can write a bit of extra JS. This should be fine for that:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> dialogs <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;dialog&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> dialogs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> <span class="token literal-property property">target</span><span class="token operator">:</span> dialog <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dialog<span class="token punctuation">.</span>nodeName <span class="token operator">===</span> <span class="token string">&quot;DIALOG&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dialog<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token string">&quot;dismiss&quot;</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><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>For the animations in and out of the top-layer we can rely on <code>@starting-style</code>. If you haven’t heard about that, I <a href="https://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style">created an article about that a little while ago</a> and the good news is that it’s part of <a href="https://web.dev/blog/interop-2024" target="_blank" rel="noreferrer noopener">Interop 2024</a>.</p> <p>It’s nice that this controls so easily, here is that CodePen demo (remember, tested in Chrome Canary)</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke dialog" src="https://codepen.io/utilitybend/embed/preview/GRLBVZX?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/GRLBVZX"> Invoke dialog</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe><small>(view invoker demos in debug mode in CodePen)</small></div> <h3 id="a-note-on-popovers">A note on popovers</h3> <p>The <code>invoketarget</code> attribute can also be used to target popovers, this will be added with the initial release of the attribute. If an element has both a <code>popovertarget</code> and <code>invoketarget</code> attribute, then <code>popovertarget</code> is ignored, or simply put: <code>invoketarget</code> takes precedence.</p> <p>The <code>invokeaction</code> attribute for popovers will handle the following options:</p> <ul> <li><code>togglePopover</code></li> <li><code>hidePopover</code></li> <li><code>showPopover</code></li> </ul> <p>These are the equivalent of the attribute <code>popovertargetaction</code> with options: <code>toggle</code>, <code>show</code>, and <code>hide</code>.</p> <p>In case you need to add other methods for closing, such as certain hardware buttons, do take a look at the <a href="https://html.spec.whatwg.org/multipage/interaction.html#the-closewatcher-interface" target="_blank" rel="noreferrer noopener">CloseWatcher API</a>. This API gets used under the hood for popovers and dialogs and is already available in <a href="https://www.youtube.com/watch?v=oqCsXbsuvM0" target="_blank" rel="noreferrer noopener">Chrome 120</a> and has a polyfill.</p> <p>What I love about these invokers is that we have so much that can be used straight out of the browser. Something that could only be achieved with huge libraries just a while ago. This is a great win for performance, but there is (potentially) a lot more to come… <strong>Let’s take a look at the potential next phases for invokers</strong>.</p> <h2 id="the-future-phase-of-invokers-invoking-details-select-and-input">The future phase of invokers: Invoking Details, Select, and Input</h2> <p>Currently planned for a future phase in the invoker plan is to give the ability to invoke the <code>&lt;details&gt;</code> element and even form controls such as the <code>&lt;select&gt;</code> or certain <code>&lt;input&gt;</code> types.</p> <p>The basics are still the same, for a <code>&lt;details&gt;</code> element we could add the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-details<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> This will open the details <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-details<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span>This is a summary<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> This is the content <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>With <code>invokeaction</code> having the following options:</p> <ul> <li><code>toggle</code></li> <li><code>open</code></li> <li><code>close</code></li> </ul> <p>Details were originally added to phase 1, but have been delayed. Let’s take a closer look at the input elements because those could potentially be a lot of fun.</p> <h3 id="showing-pickers-with-the-invokeaction-showpicker">Showing pickers with the invokeaction showPicker()</h3> <p>Imagine you want to create an <code>&lt;input type=&quot;date&quot; /&gt;</code> with a custom icon, It would look something like this:</p> <picture> <source srcset="/_astro/date-image.H92j094F_ZBx48b.avif 320w, /_astro/date-image.H92j094F_nVFkl.avif 480w, /_astro/date-image.H92j094F_tE2wM.avif 800w, /_astro/date-image.H92j094F_aa4Lm.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/date-image.H92j094F_ZqVpUH.webp 320w, /_astro/date-image.H92j094F_yxjwO.webp 480w, /_astro/date-image.H92j094F_EfFJg.webp 800w, /_astro/date-image.H92j094F_kKHXP.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/date-image.H92j094F_Z1RL0Fv.png" srcset="/_astro/date-image.H92j094F_1VPrrr.png 320w, /_astro/date-image.H92j094F_Z27QVSX.png 480w, /_astro/date-image.H92j094F_Z229zGw.png 800w, /_astro/date-image.H92j094F_Z2lDxrW.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A custom styled datepicker with a button that opens the calendar widget" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="84"> </picture> <p>Now unless you do some hacks for the icon to be clickable (I’ve done some before), it could be nice to have this as an actual button just by using HTML and CSS. In that case, this could be our markup:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>date<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Pick a date<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>input-group<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>date<span class="token punctuation">&quot;</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">&quot;</span>date<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>date<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>showPicker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- add an icon here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And by just adding a bit of styling…</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Remove the default date calendar picker */</span> <span class="token selector">[type=&quot;date&quot;]</span> <span class="token punctuation">{</span> <span class="token selector">&amp;::-webkit-calendar-picker-indicator</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> <span class="token comment">/* Style the input group with invoker */</span> <span class="token selector">.input-group</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">[type=&quot;date&quot;]</span> <span class="token punctuation">{</span> <span class="token comment">/* Presentational styles here */</span> <span class="token punctuation">}</span> <span class="token selector">[invoketarget]</span> <span class="token punctuation">{</span> <span class="token comment">/* Presentational styles here */</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This could be a result:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A datepickers picker gets invoked by clicking a button"><source src="/_astro/invoke-date.BTvexFLq.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke date" src="https://codepen.io/utilitybend/embed/preview/YzMJOov?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/YzMJOov"> Invoke date</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe><small>(view invoker demos in debug mode in CodePen)</small></div> <p>The same technique could be used to trigger a <code>&lt;datalist&gt;</code> with a separate button. Note that we need to trigger the <code>&lt;input&gt;</code> element’s <code>id</code>, not the one from the datalist:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>food<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>What would you like?<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>input-group<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">list</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>foods<span class="token punctuation">&quot;</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">&quot;</span>browser<span class="token punctuation">&quot;</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">&quot;</span>food<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>food<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>showPicker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- icon --&gt;</span> Open input picker <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>datalist</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>foods<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>🍙 Onigiri<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🍜 Ramen<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🍣 Sushi<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</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">&quot;</span>🥟 Dimsum<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>option</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>datalist</span><span class="token punctuation">&gt;</span></span> </code></pre> <video width="320" height="240" controls="" preload="metadata" aria-label="A datepickers picker gets invoked by clicking a button"><source src="/_astro/invoke-datalist.D638RPYk.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke Datalist" src="https://codepen.io/utilitybend/embed/preview/KKYryKm?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/KKYryKm"> Invoke Datalist</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe><small>(view invoker demos in debug mode in CodePen)</small></div> <p>Isn’t it great how natural this feels? The CSS also seems less hacky this way instead of doing some workaround. Giving us the option of using the browser’s built-in date or datalist option picker with a custom button. (That’s just lovely…)</p> <p>This <code>showPicker()</code> action can be used on a numerous of elements: <code>&lt;input type=&quot;color&quot; /&gt;</code> or <code>&lt;select&gt;</code> elements, or even <code>&lt;input type=&quot;file&quot; /&gt;</code>:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Upload some files?<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>uploadzone<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>showPicker<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Click this huge square to upload<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>file<span class="token punctuation">&quot;</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">&quot;</span>fileinput<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> </code></pre> <p>Here is a demo of that:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A file picker gets invoked by clicking a button"><source src="/_astro/invoke-file.2IdZJ64v.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke file" src="https://codepen.io/utilitybend/embed/preview/PogyZwe?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/PogyZwe"> Invoke file</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe><small>(view invoker demos in debug mode in CodePen)</small></div> <p>Truth be told, you could probably create this effect with the label itself, but this being an actual <code>&lt;button&gt;</code> element could be a handy tool.</p> <h3 id="number-inputs-with-custom-steppers">Number inputs with custom steppers</h3> <p>A feature that would be welcome for people who create a lot of webshops ( I am such “people” ) is the ability to add custom stepper buttons on an <code>&lt;input type=&quot;number&quot; /&gt;</code>. There is a custom implementation possible with invokers and this is how it works:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>counter<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>stepDown<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>-<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</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">&quot;</span>number<span class="token punctuation">&quot;</span></span> <span class="token attr-name">min</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</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">&quot;</span>num<span class="token punctuation">&quot;</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>num<span class="token punctuation">&quot;</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">&quot;</span>btn<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>stepUp<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>+<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Using the <code>stepDown</code> and <code>stepUp</code> events for the <code>invokeaction</code>, we can control the number of the input type. This takes into account any of the extra attributes on the input as well such as <code>min</code>, <code>max</code>, and <code>step</code>.</p> <p>Using the HTML above and some extra styling this is something I created with it:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A number input field gets increased an decreased in steps of 10 with a custom button"><source src="/_astro/invoke-number.D3pHKHfP.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke number" src="https://codepen.io/utilitybend/embed/preview/abxKpdN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/abxKpdN"> Invoke number</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe><small>(view invoker demos in debug mode in CodePen)</small></div> <p>While still a big “if” when speaking about this feature (because, well.. Experimental and such), I love the idea of implementing this with invokers. It seems that this invoker idea is fantastic when it comes to consistency, and I can only applaud that.</p> <p>There are a few caveats at the moment of writing. For the moment a <code>&quot;change&quot;</code> event doesn’t trigger when clicking our stepper buttons, this is something that has been discussed and is <a href="https://github.com/openui/open-ui/issues/1033" target="_blank" rel="noreferrer noopener">still in the works</a>.</p> <p>While we’re not done yet with all the things you can do with invokers, let’s take a little detour as events just came to mention:</p> <h2 id="custom-behaviour">Custom behaviour</h2> <p>Invokers will dispatch events on the Invokee element. Using a dash in the <code>invokeaction</code> allows for custom JavaScript to be triggered without having to wire up manual event handlers to the Invokers. By listening to the invoke event listener we can attach our action to it:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-custom<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-frobulate<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Frobulate<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>my-custom<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">&quot;my-custom&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;invoke&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>action <span class="token operator">===</span> <span class="token string">&quot;my-frobulate&quot;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">&quot;Successfully frobulated the div&quot;</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><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>(example from the <a href="https://open-ui.org/components/invokers.explainer/)" target="_blank" rel="noreferrer noopener">Open UI explainer</a>)</p> <h2 id="custom-video-and-audio-controls-with-invoke-actions">Custom video and audio controls with invoke actions</h2> <p>I’ve been showing some short demos of this while presenting about the future of UI and this is one of those things where I notice people gasping and getting a smile on their faces. Seems like people really had some struggles when it comes to custom controls for video and audio. Well, I hear you and Invokers might just make all of these things a bit more easy.</p> <p>This is how we could create a custom play button:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>video</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>custom-video<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>source</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>video</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">invoketarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>custom-video<span class="token punctuation">&quot;</span></span> <span class="token attr-name">invokeaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>play<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Play <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Keeping the consistency going (love this!), we can create an <code>invokeaction=&quot;play&quot;</code> and this will fire that play event for the video. This also works with a whole bunch of other events such as:</p> <ul> <li><code>playpause</code> (toggle between play and pause)</li> <li><code>pause</code></li> <li><code>toggleMuted</code></li> <li><code>toggleFullscreen</code></li> </ul> <p>A bit of styling and you could create something like this:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="A video has custom controls to play and pause, clicking it does make the video play and pause"><source src="/_astro/invoke-video.B9BL9-TQ.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Invoke video - Would be cool to have :playing pseudo-class - cross browser for this" src="https://codepen.io/utilitybend/embed/preview/eYoyGoW?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/eYoyGoW"> Invoke video - Would be cool to have :playing pseudo-class - cross browser for this</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>The only bit of JavaScript needed for this example is to add a class when the video is playing. This would not have been necessary if the <code>:playing</code> pseudo-class was supported everywhere, but unfortunately that isn’t the case at the moment.</p> <p>The same could be achieved for audio, I might make a demo for that later on as well.</p> <p>For more goodies and information about invokers, do check out the following links:</p> <ul> <li> <a href="https://open-ui.org/components/invokers.explainer/" target="_blank" rel="noreferrer noopener">The Open UI invokers explainer</a> </li> <li> <a href="https://github.com/keithamus/invokers-polyfill" target="_blank" rel="noreferrer noopener">Invokers polyfill by Keith Cirkel</a> </li> <li> <a href="https://demo.lukewarlow.dev/invokers/" target="_blank" rel="noreferrer noopener">This handy full info demo by Luke Warlow</a> </li> </ul> <h2 id="conclusion">Conclusion</h2> <p>I love where this invoker idea is going. If I’m honest, I was a bit skeptical at first but now that I see the potential and the possibilities when it comes to consistency, I think it’s something we need and probably long overdue. The things in Open UI are a group effort but I do want to give a shoutout to <a href="https://www.keithcirkel.co.uk/" target="_blank" rel="noreferrer noopener">Keith Cirkel</a> and <a href="https://lukewarlow.dev/" target="_blank" rel="noreferrer noopener">Luke Warlow</a>, who really have put a lot of effort into this feature and are continuing to do so. There is a lot more stuff in the works that I’d be happy to write about once they are in further stages.</p> <p>Do remember that all of this is a work in progress. But if you like this and want to support this, create demos and provide feedback. Open UI is an open group and does care a lot about developer experience. I am by no means a browser engineer, but if this one opinion of a little agency front-ender from Belgium could help to push these kinds of features, then I’d be glad to do so (every bit helps, right?).</p> <p>That being said, I do know that some of my examples might not be the perfect accessible examples at the moment. Keep in mind, that the group is working on improving accessibility, it is a topic that is highly rated.</p> <p>I do hope that by sharing these features people go check them out and give them a spin. I also hope that by writing this article and speaking about this, I can project (invoke!) a bit of my enthusiasm to others. Always feel free to tag me if you create something with them, I’d like to create a little collection on CodePen as well. Happy Invoking</p>Brecht De RuyteWhy I care about a Google Developer Expert certificate and Web communitieshttps://utilitybend.com/blog/why-i-care-about-a-google-developer-expert-certificate-and-web-communities/https://utilitybend.com/blog/why-i-care-about-a-google-developer-expert-certificate-and-web-communities/A few weeks ago, a cool little package was delivered to my home. When I opened it I couldn’t stop smiling, it was a Google Developer Expert certificate to hang on my wall. But why did this make me so happy? Well,... it’s all about being self-taught and believing that communities are important. In this (non-technical) article, I’d like to share some thoughts about communities, what they gave me, what I hope to return, and what scares me.Sun, 07 Apr 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-gde.C3_CDy8v_DdliX.avif 375w, /_astro/visual-gde.C3_CDy8v_Z2fxxEz.avif 480w, /_astro/visual-gde.C3_CDy8v_1Wsn89.avif 680w, /_astro/visual-gde.C3_CDy8v_25YIcr.avif 800w, /_astro/visual-gde.C3_CDy8v_Z20i7Aw.avif 980w, /_astro/visual-gde.C3_CDy8v_qXbYA.avif 1024w, /_astro/visual-gde.C3_CDy8v_ZzIJft.avif 1660w, /_astro/visual-gde.C3_CDy8v_1z1KAU.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-gde.C3_CDy8v_VD7OL.webp 375w, /_astro/visual-gde.C3_CDy8v_Z1W7L8L.webp 480w, /_astro/visual-gde.C3_CDy8v_2fS9DW.webp 680w, /_astro/visual-gde.C3_CDy8v_2opuIf.webp 800w, /_astro/visual-gde.C3_CDy8v_Z1GRl4I.webp 980w, /_astro/visual-gde.C3_CDy8v_ZmjVtn.webp 1024w, /_astro/visual-gde.C3_CDy8v_Z1o1RIr.webp 1660w, /_astro/visual-gde.C3_CDy8v_1VNaXB.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-gde.C3_CDy8v_Z58D4B.png" srcset="/_astro/visual-gde.C3_CDy8v_Z1f0TFF.png 375w, /_astro/visual-gde.C3_CDy8v_Vpk9I.png 480w, /_astro/visual-gde.C3_CDy8v_4e78v.png 680w, /_astro/visual-gde.C3_CDy8v_cKscN.png 800w, /_astro/visual-gde.C3_CDy8v_1bEKdL.png 980w, /_astro/visual-gde.C3_CDy8v_Z2b5kNW.png 1024w, /_astro/visual-gde.C3_CDy8v_1RoQJU.png 1660w, /_astro/visual-gde.C3_CDy8v_1Apow6.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Google Developer Experts logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>I absolutely HATE it when people call me an “influencer” or even worse, a “guru”. Still, I proudly hang my newly obtained Google certificate on the wall, perfectly visible to my webcam. The reason for this is simple: this is actually the first “piece of paper” I got, proving I can develop stuff that helps people. Sounds silly, right? I’ve made tons of websites and platforms in over a decade, so why would I even care about such a thing?</p> <p>Most people I work with have some higher degree and they all say the same thing about it: “Almost no company cares”, “I don’t even know where it is, maybe somewhere in the attic”, “It’s only handy for when you get started”,… But as I went above 30 years old, I kinda wanted a degree and swore that if I’d ever get one, I’d proudly place it somewhere in sight.</p> <p>Now, of course, A GDE Certificate is not a degree, but it felt like a “thank you” or general appreciation for doing some work (and I do study for that work). If you know me a little, you know that appreciation means something to me. So going on, I’d like to talk about some of the things I believe are important in communities and why I put in the work. I can tell you one thing for certain: it’s not about an extra income.</p> <picture> <source srcset="/_astro/gde-certificate.b1MI4Um6_12ct8v.avif 320w, /_astro/gde-certificate.b1MI4Um6_2k661h.avif 480w, /_astro/gde-certificate.b1MI4Um6_1fwErl.avif 800w, /_astro/gde-certificate.b1MI4Um6_1dUzeY.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/gde-certificate.b1MI4Um6_1VhEAC.webp 320w, /_astro/gde-certificate.b1MI4Um6_Z1Q0Qkx.webp 480w, /_astro/gde-certificate.b1MI4Um6_29BPTs.webp 800w, /_astro/gde-certificate.b1MI4Um6_280KH6.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/gde-certificate.b1MI4Um6_5jXD8.jpg" srcset="/_astro/gde-certificate.b1MI4Um6_Zc74VS.jpg 320w, /_astro/gde-certificate.b1MI4Um6_15LwUS.jpg 480w, /_astro/gde-certificate.b1MI4Um6_1d6lW.jpg 800w, /_astro/gde-certificate.b1MI4Um6_ZnXPp.jpg 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="A Google Developer Expert Certificate on the wall" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <h2 id="it-starts-with-a-shared-passion">It starts with a shared passion</h2> <p>It would be impossible to name every person who has influenced me, I wouldn’t be a developer if I had never read Andy Clarke’s Transcending CSS, heard my first UX talk by Aral Balkan, read books by Rachel Andrew, articles by Jeremy Keith, Followed an SVG course by Sara Soueidan and many more. I’m not about to go all fanboy for a bunch more of them, but those are the people who gave me a chance to become a developer even without a degree. Yes, some of them make a living out of teaching now, but that wasn’t always the case for them, they all started from one point: community and a passion for the web. This is where it started for me, and I just want to give a bit back to the community. Maybe it was becoming a dad that triggered that, or just some point in time when I thought about how hard it was just to “get that first chance”.</p> <p>A lot has changed though, In the past, the whole community could be summarized with a nice Twitter timeline without a bunch of ads and could easily be read with your morning coffee. But it’s about these people just blogging about the web, inventing some cool techniques that I could train myself in while working in a factory in the morning, and trying out some web development in the afternoon. Now this is more than a decade ago, and I never really got involved with communities much. So why now?</p> <h2 id="thinking-ill-never-be-good-enough">Thinking “I’ll never be good enough”</h2> <p>The reason I never really did a lot of writing before was fear. I always thought the following: “There are already people writing about this”, “The others have credentials”, and “Who even cares?”. And I’m still like that, this is still reflecting me in some way.</p> <p>I’ve been blogging for 3 years now, and this is the first article where I just write about “me”</p> <p>Because:</p> <ul> <li>Who even cares?</li> <li>There are more interesting people out there, so why should I write about myself</li> <li>Others do it better, and I don’t even have some credentials</li> </ul> <p>It’s not that I don’t have any other passions or hobbies besides the web: I love outdoor cooking, manga, board games, photography, building Lego, and playing a mean bit of guitar. I just don’t think I should write about these things. Truthfully, I need to set aside some of those things to stay involved with the community. But that’s ok, as long as my family comes first.</p> <p>If you have the same thing let me tell you something that helped me: When I started writing, I had about 20 readers on my articles tops and I was the most happy person ever. I told some coworkers that I started blogging and most of them reacted positively, some thought it was strange but there was this one (ex) coworker who during a little discussion said “Ow, cus you have a blog” in a very demeaning manner. I’ll add this in bold:<br> <strong>Never let these kinds of people let you down</strong>.</p> <p>Think of it like this: Even if I only had 20 readers, people read the stuff and liked it, how cool is that! But soon I noticed that whether I had 20 or 1000 readers, it didn’t matter because every article I wrote, made me learn a bit more about the subject. It made me a better developer (slow and steady). I do love that more people are reading the things I write, but I still do it out of curiosity or just simply sharing if I find something that I’m passionate about. But I still withdraw from writing personal pieces, mostly because “Who even cares?”.</p> <p>Another thing that I learned is that you should never rely on the number of readers to begin with. I had articles that I worked hard on, more than 16 hours of creating demos and just finding the right words, to notice it barely gets any views. A few weeks later, I chose to write a quick article on a Saturday morning, and it’s a hit. This doesn’t matter, If you learned something from that big article and are happy that you wrote it, that’s the important part. Unless you are writing for a company where views are important, on your personal blog, stick to things you love doing.</p> <p>So taking some of those fears into account, I think it’s understandable why I don’t write a lot of big opinionated articles and keep it technical. In contrast to that, I love reading opinionated articles and some people are just so good at it. Different points of view matter to me, which brings me to the next bit:</p> <figure class="thirds image"><picture> <source srcset="/_astro/tweet.LCaBde9o_1V7CUN.avif 320w, /_astro/tweet.LCaBde9o_2b1vzJ.avif 480w, /_astro/tweet.LCaBde9o_ZYwoqR.avif 800w, /_astro/tweet.LCaBde9o_ZHBywq.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/tweet.LCaBde9o_1DvvMs.webp 320w, /_astro/tweet.LCaBde9o_1Sporo.webp 480w, /_astro/tweet.LCaBde9o_Z1h8vzd.webp 800w, /_astro/tweet.LCaBde9o_Z10dFEL.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/tweet.LCaBde9o_Z1oVo18.png" srcset="/_astro/tweet.LCaBde9o_Zh7GOz.png 320w, /_astro/tweet.LCaBde9o_Z2dOaD.png 480w, /_astro/tweet.LCaBde9o_1RpoBG.png 800w, /_astro/tweet.LCaBde9o_29kew8.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Tweet by @utilitybend with text: I wrote a personal article about the fear of writing opinionated or personal articles and now I’m in serious doubt of publishing it… that’s irony, I guess." loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture><figcaption>And here it is…</figcaption></figure> <h2 id="getting-involved-with-web-communities">Getting involved with web communities</h2> <p>For the moment, I’m part of some internal communities at iO (my job), some W3C open community groups (Open UI and CSS4), and of course the GDE community. The most interesting part is learning from people’s opinions. It all starts with listening and asking questions.</p> <p>As an example of communities: In Open UI you have so many types of people: browser engineers, accessibility experts, CSS experts, and Maintainers of design systems, It’s a fantastic group that deeply shows the difference in what matters most to certain groups of people. In my eyes, it’s a great experience to be part of. I mostly create some demos and spend some time asking questions, adding small contributions, or giving an opinion here and there, and people listen, help, and are respectful.</p> <p>It’s a great example of how a community should be. And it starts with yourself, I know I said a lot of dumb things as well, but that’s great, it helps me move forward and learn. Believe it or not, it can also help the community, because sometimes, “that dumb thing”, Is the smart question people forget to think of and helps to move things forward. Also, I had never spoken to browser engineers before, and being part of this group made me really respect the work they do even more. (Almost made me want to give it a go as well ;) )</p> <p>The reason why I speak so passionately about this group is because I’m being sincere, I mean every word. And that - once again - brings me to the following:</p> <h2 id="be--grateful---always">Be ******* grateful - always!</h2> <p>When someone helps me with a quick finish of a demo, helps me out by explaining some new specs a bit closer, or reviews an article or a demo I’ve written, I thank them. Maybe_ a bit too much_ but I mean it, every time. When people take time for you, time that they could’ve easily spent on something else, that’s golden. When I give a presentation my last sentence is “thank you” not because it should be, but because I mean it. I am happy to be invited to speak, happy to write, and to maybe help a few people find some answers. I’m not an AI, I’m a human being, spending hours a week next to my day job for the sake of the community. And to all those others doing the same thing, remember that you could be the one that brings a low-educated factory worker to a web development job.</p> <p><em>Even when your opinions clash, be respectful, and give your opinion but also “pick your battles”.</em></p> <h2 id="why-i-suggest-everyone-should-get-involved-with-at-least-one-community">Why I suggest everyone should get involved with at least one community</h2> <p>If you’re a developer, chances are high there is something you are passionate about. It might not be the same thing as your day-to-day job, but that doesn’t matter. There are many ways you can get involved with open source projects, W3C community groups, Frameworks, take your pick… Every bit you do to contribute helps. Whether that is commenting on issues, helping with an implementation or even just listening and spreading the word. It can make you grow as a person and you would be surprised by how little this world of web developers is. I’m not suggesting everyone should blog about the work they do, hell no, I might’ve started an outdoor cooking blog and I even used to have a photography blog long ago. Write what you are passionate about. But you’d be surprised how much an hour/week involvement in web communities can make you grow.</p> <p>This is how it all started for me, by “joining stuff and”, “listening about stuff”, and now I get to speak about stuff to students and others.</p> <p>I still have so much to learn, and so many things I want to do, I’m glad that people and initiatives such as the GDE program help me with that, spreading my enthusiasm for Web UI.</p> <p>For now, I’m trying not to undersell myself, and celebrate some of the achievements. This is probably the hardest thing for me completely, because deep inside: I’m still that 20-year-old, working in the factory morning shift, trying to learn things that all these amazing people are writing about. But maybe that’s my strength, who knows…</p> <p>For me, my passion for the web has always been UI and CSS, and I love sharing this learning journey with you on this blog. So yes, this was a little personal blog post and I probably won’t be adding much of those soon, but still, I’m happy I did.</p>Brecht De RuyteAnimating clip paths on scroll with @property in CSShttps://utilitybend.com/blog/animating-clip-paths-on-scroll-with-at-property-in-css/https://utilitybend.com/blog/animating-clip-paths-on-scroll-with-at-property-in-css/While experimenting with CSS, you sometimes discover a technique you want to do more of. This is one of those discoveries for me... I love how we can now animate clip paths on scroll with CSS. From animating an image into a star to creating Polaroid-like images on scroll. In this article, I’d like to demo some techniques we can use to create these interesting effects using clip paths, @property, and even container units to create visually pleasing scroll-driven animations.Tue, 26 Mar 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-clip-path-at-property-scroll.DAFTCtyx_1nlDJ3.avif 375w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z13XsEX.avif 480w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Zcuv3n.avif 680w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_1SVtaX.avif 800w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_ZRH8Ps.avif 980w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z1KsFom.avif 1024w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_yQ7mr.avif 1660w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z1Aza7g.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-clip-path-at-property-scroll.DAFTCtyx_ZmWvjs.webp 375w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_2fTv5s.webp 480w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z1WNF6S.webp 680w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_8Cj7s.webp 800w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_2raOTX.webp 980w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Zm1caP.webp 1024w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_1XiAzX.webp 1660w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_18Mwcw.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z1DYhlb.jpg" srcset="/_astro/visual-clip-path-at-property-scroll.DAFTCtyx_ZmnGuk.jpg 375w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_2gtjTA.jpg 480w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z1WeQhK.jpg 680w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_9c7VA.jpg 800w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_2rJDJ6.jpg 980w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z4jRGL.jpg 1024w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_2fYU42.jpg 1660w, /_astro/visual-clip-path-at-property-scroll.DAFTCtyx_Z25QEj.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A polaroid like frame holding an image, with a rotate and scroll icon next to it" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>There are a lot of features coming in CSS and one of the things I often think about is how to combine all of them. How can I create some practical use cases for all this stuff? So, in this article, you will see a lot of things: Scroll-driven animations, @property, container units, clamping, and other goodies. Time to write some modern CSS</p> <h2 id="clipping-and-animating-on-scroll">Clipping and animating on scroll</h2> <p>Before we get to the big demo, let’s start with a smaller one to showcase some of the basic techniques we’ll be using later on and since I’ve already written about <a href="https://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css">the basics of scroll-driven animations</a>, let’s dive right into animating clip paths on scroll. This is the first effect we’re going to create:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/clipping-output.Bsbp6v-K.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>In this demo, we have an image set fixed to the center of our screen. For demo purposes, let’s give the <code>&lt;body&gt;</code> a height of <code>300vh</code> to have some overflow to play around with. This is the CSS attached to the image, the bottom three properties are the ones we’ll be working with:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> fixed<span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">left</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>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">)</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 property">max-width</span><span class="token punctuation">:</span> 50vmin<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">aspect-ratio</span><span class="token punctuation">:</span> 1<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">border</span><span class="token punctuation">:</span> 5px solid deeppink<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>20% at 0% 0%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> rotateOrb linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>When we want to animate clip-paths there are two options we can go for.</p> <h3 id="option-1-just-animate-the-property">Option 1: Just animate the property</h3> <p>The easiest way to do this would be to just animate that property again and again. When choosing that route, this would be the animation for what we want to achieve:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> rotateOrb</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>20% at 0% 0%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">25%</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>20% at 100% 0%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>20% at 100% 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">75%</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>60% at 0% 100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span>150% at 0% 0%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Now, before I get a whole bunch of hate: Yes, this will be the shortest amount of code needed to create that effect, but for me, this always felt bloated, especially when using bigger animations. Circles are quite easy to understand because there are only 3 values involved, but when using polygons, it gets tricky. We could benefit from using something more understandable. Which brings us to the next part.</p> <h3 id="option-2-using-custom-properties">Option 2: Using custom properties</h3> <p>Let’s make all of this a bit more readable by using custom properties. This would make it a bit easier to tweak as well. So let’s change our code to set this up. First up, let’s update our code to animate variables instead of the <code>clip-path</code> property:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token comment">/* positioning properties */</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">circle</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--scale<span class="token punctuation">)</span> at <span class="token function">var</span><span class="token punctuation">(</span>--move-x<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--move-y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> rotateOrb linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><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">@keyframes</span> rotateOrb</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">25%</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">75%</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--scale</span><span class="token punctuation">:</span> 60%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--scale</span><span class="token punctuation">:</span> 150%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>One might think that the only thing left to do is to set up our basic variables inside of the root (spoiler alert, this won’t work):</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--move-x</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--move-y</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--scale</span><span class="token punctuation">:</span> 20%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This results in the following “jumpy effect”:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/clipping-jumpy.DMpagoCf.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h4 id="so-why-doesnt-this-work">So, why doesn’t this work?</h4> <p>At this point, the browser has no idea that it should animate these properties as a percentage, as custom properties can contain anything you want. Thankfully, we have something available in every major browser that could help with that issue, which is the <code>@property</code> syntax.</p> <p>Instead of just adding the custom properties inside the root, let’s add them using <code>@property</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --move-x</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0%<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 punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --move-y</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0%<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 punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --scale</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 20%<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 punctuation">}</span> </code></pre> <p>Now our demo will fully work. Here is the CodePen of this:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll driven animations circular reveal" src="https://codepen.io/utilitybend/embed/preview/GRLmaBr?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/GRLmaBr"> Scroll driven animations circular reveal</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>So, as a conclusion to this: Yes, the code is a bit bigger, but it feels a lot smarter and I do think it creates a bit more control and easy tweaking. In the CodePen above, you could give the image a class <code>.square-in</code> which would trigger a new animation, using those same custom properties. So, yes, for a small demo like this, it might be a bit too much But looking at the bigger picture, it will be a lot easier to maintain, especially when using multiple animations in a project.</p> <p>Another quick example of this is animating a polygon star:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Animating to a star on scroll" src="https://codepen.io/utilitybend/embed/preview/mdgWdNY?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/mdgWdNY"> Animating to a star on scroll</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I have no idea why I created this, but now, if you need it, you don’t have to :) I’ve also written another article if you’d like some <a href="https://utilitybend.com/blog/taking-a-closer-look-at-property-in-css">extra information about @property</a>.</p> <p>Alright, believe it or not, this was the intro. Now that we’ve seen this technique, buckle up, and let’s create the actual demo:</p> <h2 id="animating-a-css-journal-with-scroll-driven-animations-property-and-clip-paths">Animating a CSS Journal with scroll-driven animations, @property, and clip-paths</h2> <p>In this step-by-step tutorial, we’ll be making a sort of CSS Journal. As I’ve recently been playing with the idea of keeping a journal in real life, I felt a bit inspired so, that’s kind of where this idea came from. The intake is to have a picture and a quote next to each other and we’ll be giving them a cool entry into the viewport. In the final demo, there will also be a little intro to notify users to scroll, but that’s a bit beside the point of the demo. Here is the final result of what we’ll be making:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/base-demo-compressed.FuoFgTTP.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h3 id="setting-up-the-html-of-our-journal">Setting up the HTML of our journal</h3> <p>Let’s start with setting up the HTML, we will alternate the image and text in the DOM for now as we’ll play around with that bit later on. So, without the actual content, this is our HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>entry<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">&gt;</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">&quot;</span>entry<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figcaption</span><span class="token punctuation">&gt;</span></span>...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figcaption</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- this gets repeated a few times --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p><strong>Let’s get over the function of our HTML elements for this demo:</strong></p> <ul> <li>The <code>&lt;section&gt;</code> will hold our articles, displaying them in a flex column</li> <li>The <code>&lt;article&gt;</code> will be used as a wrapper for our items and add a grid layout</li> <li>The <code>&lt;figure&gt;</code> will hold the image and will rotate a bit when entering the viewport</li> <li>The <code>&lt;img&gt;</code> will get an animated <code>clip-path</code> to create the Polaroid film effect</li> <li>The <code>&lt;figcaption&gt;</code> will be used as a description on the bottom of the polaroid</li> <li>Then <code>.entry</code> will fade in a bit when entering the viewport, first the lines of the text, followed-up by the text itself.</li> </ul> <h3 id="the-basic-layout---a-reset-and-organizing-our-entries">The basic layout - a reset and organizing our entries</h3> <p>First of all, we’ll need a little reset, let’s get that out of the way right now. I also added a <a href="https://fonts.google.com/specimen/Annie+Use+Your+Telescope" target="_blank" rel="noreferrer noopener">custom Google font</a> for presentational reasons:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">body</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">font-family</span><span class="token punctuation">:</span> <span class="token string">&quot;Annie Use Your Telescope&quot;</span><span class="token punctuation">,</span> cursive<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">radial-gradient</span><span class="token punctuation">(</span>ivory 70%<span class="token punctuation">,</span> transparent 30%<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">radial-gradient</span><span class="token punctuation">(</span>ivory 70%<span class="token punctuation">,</span> transparent 30%<span class="token punctuation">)</span><span class="token punctuation">,</span> #f5f3f3<span class="token punctuation">;</span> <span class="token property">background-size</span><span class="token punctuation">:</span> 5px 5px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figure</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 property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">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 property">max-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Next up, let’s create a flex column out of our <code>section</code> and give the items a bit of a <code>gap</code>, this will help the scrolling experience later on, we can also provide a <code>padding-block: 50vh;</code> to make sure there is enough spacing to view the scroll animations of all the items:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">section</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-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 40vmax<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 property">padding-inline</span><span class="token punctuation">:</span> 10vmax<span class="token punctuation">;</span> <span class="token property">padding-block</span><span class="token punctuation">:</span> 50vh<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 1400px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Now let’s turn all of our <code>&lt;article&gt;</code> elements into a grid (I used a breakpoint of <code>750px</code> for this, but choose freely). Our image will be set to be 2/5 of the width. By using <code>:has()</code> we can check where the image is located and change our grid based on that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">article</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">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 8vmax<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> 750px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 2fr 3fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 10vmax<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;:has(.entry + figure)</span> <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> 750px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 3fr 2fr<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="styling-and-animating-the-entry-fade">Styling and animating the entry fade</h3> <p>This is where our first scroll-driven animation gets triggered, we’ll want to slightly animate the entry upwards into place while also changing the text color from transparent to black. We will animate the color instead of the opacity, because we’ll want to use the opacity property for the lines, as stated before: “The .entry will fade in a bit when entering the viewport, first the lines of the text, followed-up by the text itself.”</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.entry</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> revealEntry linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> cover 20% contain 40%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> revealEntry</span> <span class="token punctuation">{</span> <span class="token selector">from</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> transparent<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>20%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">to</span> <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 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> <span class="token punctuation">}</span> </code></pre> <p>One of the things you might be wondering is how we can find the perfect <code>animation-range</code> for these effects. For that, I usually open a tab and play around with the <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges/#range-start-name=cover&range-start-percentage=20&range-end-name=contain&range-end-percentage=40&view-timeline-axis=block&view-timeline-inset=0&subject-size=smaller&subject-animation=reveal&interactivity=clicktodrag&show-areas=yes&show-fromto=yes&show-labels=yes" target="_blank" rel="noreferrer noopener">ranges tool created by Bramus</a>. He made a Chrome plugin for it as well, but I like this one a bit better in my workflow, the choice is yours of course.</p> <p>Next up, is to create the lines for our text and slightly make those appear a bit earlier, this will just be a simple basic fade-in animation. This is the complete code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.entry p</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> 0.9891rem + 1.3043vi<span class="token punctuation">,</span> 2rem<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">repeating-linear-gradient</span><span class="token punctuation">(</span> to top<span class="token punctuation">,</span> #95d9c3 0 0.125rem<span class="token punctuation">,</span> transparent 0.125rem 1lh <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background-position-y</span><span class="token punctuation">:</span> 0.8lh<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> fadein linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> entry 10% contain 10%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> fadein</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>The lines are created with a <code>repeating-linear-gradient</code> using the <code>lh</code> unit, a relative CSS unit based on the <code>line-height</code> of the current font. This is easy for re-sizing the text, especially if you’re using a bit of fluid typography with <code>clamp()</code>. I recently wrote a <a href="https://techhub.iodigital.com/series/going-beyond-pixels-and-rems-in-css" target="_blank" rel="noreferrer noopener">series about relative length units in CSS</a> if you want to read a bit more about these techniques. But for this demo, I’m just adding it as a sweet bonus.</p> <p><strong>For the moment, you should be having something like this:</strong></p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/current-state-check-compressed.DaS0zexD.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h3 id="rotating-our-figures-as-they-enter-the-screen">Rotating our figures as they enter the screen</h3> <p>Before we get to the clipping, let’s start with the rotation of our <code>&lt;figure&gt;</code>. To do this, we’ll add some default styling, and give the figure an <code>aspect-ratio: 1;</code>. This makes perfect sense because these kinds of pictures are usually in a square aspect ratio:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">figure</span> <span class="token punctuation">{</span> <span class="token property">--rotate</span><span class="token punctuation">:</span> 20deg<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">aspect-ratio</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <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.1<span class="token punctuation">)</span> 0px 4px 12px<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> rotate linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> cover 20% contain 30%<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">max-width</span><span class="token punctuation">:</span> 749px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 350px<span class="token punctuation">;</span> <span class="token property">align-self</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>We also added a custom property <code>--rotate</code> to handle the rotation of our figure, we’ll be using that custom property in our keyframes:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> rotate</span> <span class="token punctuation">{</span> <span class="token selector">entry 100%, exit 0%</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">rotate</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rotate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">exit 100%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">rotate</span><span class="token punctuation">:</span> 0deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Pretty sweet! If you’re actually following along with the demo, you might notice that all our figures are rotating in the same direction. It’s <code>:has()</code> to the rescue! Remember the following line in our article, and let’s update it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">&amp;:has(.entry + figure)</span> <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> 750px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 3fr 2fr<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figure</span> <span class="token punctuation">{</span> <span class="token property">--rotate</span><span class="token punctuation">:</span> -20deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This will ensure that the image will rotate in the other direction when placed after the entry…<br> <em><code>:has()</code> is so freaking awesome!</em></p> <h3 id="creating-our-image-to-polaroid-effect-with-clip-paths-and-property">Creating our image to Polaroid effect with clip-paths and @property</h3> <p>To create our frame, we will only need to animate two values, as our frame has 3 equally sized sides and only the bottom part is wider. Let’s create two custom properties for the clipping and set the <code>initial-value</code> to <code>0%</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --clip-1</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0%<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 punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --clip-2</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> 0%<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 punctuation">}</span> </code></pre> <p>For our image, we’ll add the default styling, giving it an <code>aspect-ratio: 1;</code> while also clipping it with <code>inset</code>. The values of inset clipping are familiar, it’s the same as margins: <em>top, left, bottom, and right.</em> So this will translate to:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token function">inset</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-2<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>Here is the full styling of our image:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">img</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">object-fit</span><span class="token punctuation">:</span> cover<span class="token punctuation">;</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">inset</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-2<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> createFrame linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> cover 15% contain 40%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>In the animation, we will update these values and add a shadow:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> createFrame</span> <span class="token punctuation">{</span> <span class="token selector">to</span> <span class="token punctuation">{</span> <span class="token property">--clip-1</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span> <span class="token property">--clip-2</span><span class="token punctuation">:</span> 15%<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 6px 12px -2px<span class="token punctuation">,</span> <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.3<span class="token punctuation">)</span> 0px 3px 7px -3px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>You should now have something like this:</p> <picture> <source srcset="/_astro/almost-there-clipping.CU-YFC2j_ZildXL.avif 320w, /_astro/almost-there-clipping.CU-YFC2j_Zu9yD2.avif 480w, /_astro/almost-there-clipping.CU-YFC2j_1sVQOX.avif 800w, /_astro/almost-there-clipping.CU-YFC2j_2jFIil.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/almost-there-clipping.CU-YFC2j_Z2fN1oR.webp 320w, /_astro/almost-there-clipping.CU-YFC2j_Z2rBm48.webp 480w, /_astro/almost-there-clipping.CU-YFC2j_ZtuUA8.webp 800w, /_astro/almost-there-clipping.CU-YFC2j_mdURf.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/almost-there-clipping.CU-YFC2j_Z24BUxi.png" srcset="/_astro/almost-there-clipping.CU-YFC2j_1IIuF7.png 320w, /_astro/almost-there-clipping.CU-YFC2j_1wUa0Q.png 480w, /_astro/almost-there-clipping.CU-YFC2j_Z1zaxk5.png 800w, /_astro/almost-there-clipping.CU-YFC2j_ZIqFQH.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="All hearts are visible in the browser window, but unortunatly sticking to the top." loading="lazy" decoding="async" fetchpriority="auto" width="70" height="33"> </picture> <p>We’re getting close, two steps remain.</p> <h3 id="animating-the-caption-into-the-frame">Animating the caption into the frame</h3> <p>We will be absolute positioning the <code>&lt;figcaption&gt;</code> while also giving it a <code>max-width</code> with a <code>text-overflow: ellipsis</code>. But since our frame changes depending on the width of the window, It might be interesting to size and position the caption based on its container.</p> <p>To do this, let’s create a container out of our figure:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">figure</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> frame<span class="token punctuation">;</span> <span class="token property">container-type</span><span class="token punctuation">:</span> size<span class="token punctuation">;</span> <span class="token comment">/* previous code... */</span> <span class="token punctuation">}</span> </code></pre> <p>Now, in normal cases, it would be a problem to set the <code>container-type</code> to <code>size</code> because when using a block axis, this container needs to have some sort of defined height. Luckily, thanks to the <code>aspect-ratio</code>, this has been set.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">figcaption</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-inline</span><span class="token punctuation">:</span> 5cqw<span class="token punctuation">;</span> <span class="token property">bottom</span><span class="token punctuation">:</span> 1.2cqh<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 8cqh<span class="token punctuation">;</span> <span class="token property">text-overflow</span><span class="token punctuation">:</span> ellipsis<span class="token punctuation">;</span> <span class="token property">white-space</span><span class="token punctuation">:</span> nowrap<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 property">z-index</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>So all that is left for us to do, is to make that text appear, once again, using an inset clip-path where we will animate that one custom property that we already created:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">figcaption</span> <span class="token punctuation">{</span> <span class="token property">--clip-1</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token comment">/* previous presentational styles */</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">inset</span><span class="token punctuation">(</span>0 <span class="token function">var</span><span class="token punctuation">(</span>--clip-1<span class="token punctuation">)</span> 0 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> revealText linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> cover 25% contain 35%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> revealText</span> <span class="token punctuation">{</span> <span class="token selector">to</span> <span class="token punctuation">{</span> <span class="token property">--clip-1</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="be-weary-of-user-preferences-and-browser-support">Be weary of user preferences and browser support</h3> <p>One last thing to end this demo, is that you should always think about user preferences and browsers support.</p> <p>As a best practice, you should do this as a progressive enhancement. But for demo reasons, I decided to use the not operator in a support flag, mostly to not overly pollute the demo:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><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">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>block<span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.intro</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">section *</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figure</span> <span class="token punctuation">{</span> <span class="token property">rotate</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--rotate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figcaption</span> <span class="token punctuation">{</span> <span class="token property">--clip-1</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">img</span> <span class="token punctuation">{</span> <span class="token property">--clip-1</span><span class="token punctuation">:</span> 5%<span class="token punctuation">;</span> <span class="token property">--clip-2</span><span class="token punctuation">:</span> 15%<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 6px 12px -2px<span class="token punctuation">,</span> <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.3<span class="token punctuation">)</span> 0px 3px 7px -3px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>You could use the same approach for users that prefer reduce motion. In this case, the same code can be inside of it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>prefers-reduced-motion<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Same as the support query content */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="the-final-result">The final result</h2> <p>If you followed along with the article, you should have ended with a <a href="https://codepen.io/utilitybend/pen/poBPXqo" target="_blank" rel="noreferrer noopener">similar result as the demo I’m embedding to this page</a>.</p> <p>In this final version, I also added some cascade layers into the mix for easy grouping and a little intro, but that’s the only difference.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="CSS Journal (@property, Container units and Scroll Driven Animations)" src="https://codepen.io/utilitybend/embed/preview/zYXwvyZ?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/zYXwvyZ"> CSS Journal (@property, Container units and Scroll Driven Animations)</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>I do hope you enjoyed this little showcase of modern CSS features, I had a blast creating this demo and looking forward to seeing what other magical things people will be making with scroll-driven animations combined with clip-paths. After being busy with mostly HTML and web components, it was nice to return to CSS a bit. I really enjoyed it. One question remains: Will I start creating a real physical journal and start “journaling”, I’m still not sure, but at least, this little demo sprouted from that idea.</p>Brecht De RuyteA Valentine's Day special: A web component to show hearts on your websitehttps://utilitybend.com/blog/a-valentines-day-special-a-web-component-to-show-hearts-on-your-website/https://utilitybend.com/blog/a-valentines-day-special-a-web-component-to-show-hearts-on-your-website/While learning about web components a few months ago, I wanted to create my first (useless) little component as a package on NPM. In this article a little tutorial on how I created my first basic web component with LIT, Typescript, using an easy setup with Vite. I will be sharing how to do the setup, some of the ideas behind it, and some of the gotchas.Wed, 14 Feb 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-hearts.CriwBp2-_1mAFhT.avif 375w, /_astro/visual-hearts.CriwBp2-_1YpdB2.avif 480w, /_astro/visual-hearts.CriwBp2-_14mw2k.avif 680w, /_astro/visual-hearts.CriwBp2-_Z2oNRzf.avif 800w, /_astro/visual-hearts.CriwBp2-_Z2rPAWV.avif 980w, /_astro/visual-hearts.CriwBp2-_2lp0Ow.avif 1024w, /_astro/visual-hearts.CriwBp2-_1FzNFn.avif 1660w, /_astro/visual-hearts.CriwBp2-_Z2uaTh3.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-hearts.CriwBp2-_Z2iSQCy.webp 375w, /_astro/visual-hearts.CriwBp2-_Z1G5jjq.webp 480w, /_astro/visual-hearts.CriwBp2-_2t47UN.webp 680w, /_astro/visual-hearts.CriwBp2-_Z107gFL.webp 800w, /_astro/visual-hearts.CriwBp2-_Z13904s.webp 980w, /_astro/visual-hearts.CriwBp2-_8gs8Q.webp 1024w, /_astro/visual-hearts.CriwBp2-_ZwxJ0i.webp 1660w, /_astro/visual-hearts.CriwBp2-_1g00c.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-hearts.CriwBp2-_Z1FC0Fw.jpg" srcset="/_astro/visual-hearts.CriwBp2-_ZaBMf3.jpg 375w, /_astro/visual-hearts.CriwBp2-_rbK45.jpg 480w, /_astro/visual-hearts.CriwBp2-_ZsPVuC.jpg 680w, /_astro/visual-hearts.CriwBp2-_189MGJ.jpg 800w, /_astro/visual-hearts.CriwBp2-_1584j3.jpg 980w, /_astro/visual-hearts.CriwBp2-_elNrs.jpg 1024w, /_astro/visual-hearts.CriwBp2-_ZqsnGG.jpg 1660w, /_astro/visual-hearts.CriwBp2-_Z1XdjlV.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Multicolored hearts randomly placed on a black background, yellow, red, blue and green" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>Each year I add an article on the 14th of February. Not because I’m a big fan of the commercial day called Valentine’s Day, but rather that I started it off as a laugh and somewhat stuck with it. Now It feels like something I have to do, but it does help me to invent something new each year, and since I was learning a bit more about web components at the end of 2023. I thought it would be cool to create a little web component for this day, giving users a little plug-and-play option for animated hearts on their web page.</p> <h2 id="setting-up-our-workspace-with-vite">Setting up our workspace with Vite</h2> <p>Let’s start by setting up our default workspace, Open up the terminal and navigate to the place where you want to install the web component:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token builtin class-name">cd</span> ./projects/ </code></pre> <p>Initialize Vite</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> create vite@latest </code></pre> <p>Install dependencies if needed. In my example, the project is named <code>for-love</code></p> <ul> <li>Next choose <code>Lit</code> as a framework</li> <li>As a variant in this case, choose <code>Typescript</code></li> <li>Change the directory to the folder, npm install, and npm run dev as described after initializing Vite.</li> </ul> <h3 id="why-choose-typescript-for-a-web-component">Why choose typescript for a web component</h3> <p>To be completely honest, I added the Typescript mostly out of peer pressure. Typescript is an important thing, especially in big projects, but if you ask my personal opinion, for a simple web component like this, it might be a bit too much.</p> <h2 id="adding-lit-for-the-developer-experience">Adding Lit for the developer experience</h2> <p>The Lit library makes it easier and more efficient to work with web components. It provides a more declarative syntax for defining components. This can make our code more readable and maintainable compared to the imperative style required when working directly with the Web Components API. In this tutorial, we’ll be using this library mostly for the basic features of developer experience, but it has a lot more to offer (I’m still learning about it myself).</p> <h2 id="setting-up-the-basics-of-our-for-love-web-component">Setting up the basics of our for-love web component</h2> <p>For this tutorial, it might be a good idea to make the component grow a bit, almost in the same way it was set up from the beginning, and expand it later on. Not every little detail of the web component will be covered in this article, for example, the “contained” and ”multicolor” options will not be added.</p> <p>So for this for-love-element, we want a few options:</p> <ul> <li>Easily change the amount of hearts with an attribute</li> <li>Randomly place the hearts</li> <li>Change the size and color through CSS custom properties</li> <li>Change the amount the hearts sway from left to right with custom properties</li> <li>Change animation duration, iteration, and easing with custom properties</li> </ul> <p>Let’s start with setting up the basic component by reading the amount of hearts as an option and iterating that amount to show a bunch of hearty divs:</p> <p>Let’s remove everything inside the <code>src</code> folder created by the Vite install, and let’s create our new file called <code>for-love.ts</code>. Let’s add the following.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> html<span class="token punctuation">,</span> css<span class="token punctuation">,</span> LitElement <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">&#39;lit&#39;</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> customElement<span class="token punctuation">,</span> property <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">&#39;lit/decorators.js&#39;</span><span class="token punctuation">;</span> @<span class="token function">customElement</span><span class="token punctuation">(</span><span class="token string">&#39;for-love&#39;</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ForLove</span> <span class="token keyword">extends</span> <span class="token class-name">LitElement</span> <span class="token punctuation">{</span> @<span class="token function">property</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> Number <span class="token punctuation">}</span><span class="token punctuation">)</span> amount <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token keyword">static</span> styles <span class="token operator">=</span> css<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> /* we will add our styles here */ </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> &lt;div class=&quot;for-love-hearts&quot; part=&quot;hearts-container&quot;&gt; </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>amount<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">&#39;heart&#39;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">index</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class=&quot;heart&quot; data-item=&quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>index<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot;&gt;Heart&lt;/div&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> &lt;/div&gt; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> declare global <span class="token punctuation">{</span> <span class="token keyword">interface</span> <span class="token class-name">HTMLElementTagNameMap</span> <span class="token punctuation">{</span> <span class="token string-property property">&#39;for-love&#39;</span><span class="token operator">:</span> ForLove<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Inside of our <code>index.html</code> change the <code>&lt;my-element&gt;</code> custom element to our <code>&lt;for-love&gt;</code> element. You should now see “Heart” printed 10 times. Inside our <code>&lt;for-love&gt;</code> element, we can adjust the amount by adding <code>&lt;for-love amount=”40”&gt;&lt;/for-love&gt;</code>. Go on, give it a try…</p> <p>So, what we did here: We included <code>html</code>, <code>css</code> and <code>LitElement</code> from Lit. This last one is needed to create our custom element with Lit, while <code>html</code> and <code>css</code> give us the possibility to add markup and styling through that cool template literal system. We added a placeholder for our CSS and returned some HTML when the component loads. Inside this component, we add a loop that iterates over our set amount property.</p> <p>This <code>@property({ type: Number }) amount = 10;</code> decorator defines a public property named <code>amount</code> with a type annotation of <code>Number</code>. This ensures type safety and lets Lit know how to update the property based on its type. We will be needing this because It might not be the best Idea to allow just about any number of hearts in our web component, so let’s restrict it. The following bit of code will check if the amount is more than 400, If so, it will just return 400.</p> <p>Let’s remove</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"> @<span class="token function">property</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> Number <span class="token punctuation">}</span><span class="token punctuation">)</span> amount <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span> </code></pre> <p>And replace it with:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"> <span class="token keyword">private</span> _maxAmount <span class="token operator">=</span> <span class="token number">400</span><span class="token punctuation">;</span> <span class="token comment">// Set your maximum value here</span> @<span class="token function">property</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> Number <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">get</span> <span class="token function">amount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_amount<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">set</span> <span class="token function">amount</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">value</span><span class="token operator">:</span> number <span class="token operator">|</span> string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> parsedValue <span class="token operator">=</span> <span class="token keyword">typeof</span> value <span class="token operator">===</span> <span class="token string">&#39;number&#39;</span> <span class="token operator">?</span> value <span class="token operator">:</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Check if the parsed value is a valid number</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isNaN</span><span class="token punctuation">(</span>parsedValue<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Set the value to the maximum if it exceeds the maximum allowed</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_amount <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>parsedValue<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_maxAmount<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">&#39;Invalid value for amount property. Please provide a valid number.&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">requestUpdate</span><span class="token punctuation">(</span><span class="token string">&#39;amount&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Notify LitElement to update the property</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> _amount <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span> </code></pre> <h3 id="explaining-the-maximum-value-code">Explaining the maximum value code</h3> <p>First, we define a private property named <code>_maxAmount</code> and set it to <code>400</code>. This variable determines the highest allowed value for the amount property. This is our safety net, maybe 400 is too much, I’ll let the reader be the judge of that.</p> <p><strong>Creating the Property:</strong></p> <p>Next, we declare a public property called <code>amount</code>. Here, we use the <code>@property</code> decorator and specify the type again as <code>Number</code>. This not only clarifies the expected data type but also helps Lit understand how to update the property efficiently, which is one of those superpowers of the library.</p> <p>We then create a getter and a setter for the <code>amount</code> property. The getter simply returns the current value stored in the private <code>_amount</code> property, providing read access.</p> <p>The setter is where the magic happens. It handles setting the <code>amount</code> property, accepting either a number or a string as input. Let’s delve into the steps it takes:</p> <ol> <li><strong>Parsing the Input:</strong> The code checks if the input is already a number. If so, it’s used directly. If it’s a string, the code attempts to convert it to a number using <code>parseInt</code>. This ensures we’re working with numerical values.</li> <li><strong>Checking for Validity:</strong> The code uses the <code>isNaN</code> function to verify if the parsed value is a valid number. If it’s not, it means the input wasn’t a proper number and a warning message is logged to the console to alert the user.</li> <li><strong>Enforcing the Limit:</strong> If the parsed value is valid, the code compares it to the <code>_maxAmount</code>. Using the <code>Math.min</code> function, it sets the <code>_amount</code> property to the smaller of these two values. This effectively enforces the maximum limit you defined earlier.</li> <li><strong>Triggering an Update:</strong> After setting the <code>_amount</code> property, the code calls <code>this.requestUpdate(&#39;amount&#39;)</code>. This tells Lit that the property has changed and prompts the component to update itself with the new value. This ensures your UI reflects the latest accepted input, (aka the amount set in the attribute).</li> </ol> <p>Finally, we set the initial value of the <code>_amount</code> property to <code>10</code>, our default value.</p> <h2 id="creating-our-hearts-animation-with-css-and-custom-properties">Creating our hearts animation with CSS and custom properties</h2> <p>Time for us to add some love to our component and style our hearts using CSS. We will be using custom properties to create the ability for options in easily. Let’s go inside of our CSS template literals and add the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:host</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>--fl-heart-color<span class="token punctuation">,</span> #F8C8DC<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--_size</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--fl-heart-size<span class="token punctuation">,</span> 3vw<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--_sway</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--fl-sway<span class="token punctuation">,</span> 5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--_iteration</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--fl-iteration<span class="token punctuation">,</span> infinite<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--_duration</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--fl-duration<span class="token punctuation">,</span> 10s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--_ease</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--fl-ease<span class="token punctuation">,</span> ease-in-out<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.for-love-hearts</span> <span class="token punctuation">{</span> <span class="token property">position</span><span class="token punctuation">:</span> fixed<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 property">pointer-events</span><span class="token punctuation">:</span> none<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> </code></pre> <p>If you haven’t heard of <code>:host</code> before, this is a CSS pseudo class that is used to style our custom element itself, this is a great place to store our variables.</p> <p>Using underscore variants as private variables in these custom properties, we can read if a variable is present or else use a fallback. I like this way of working, it’s pretty clean. Our fixed container is going to fill in the whole window and pointer events will be set to none. This is the container that will contain our hearts.</p> <p>Now let’s create our hearts with CSS only, I’m not going to go into much detail about this technique as this is a bit off-topic. The important part here is to notice the usage of custom properties, we will be placing these hearts in a random matter across the width of the screen. For that, we will need a new custom property called <code>--left</code>.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.heart</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> 0<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--left<span class="token punctuation">,</span> 0<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">var</span><span class="token punctuation">(</span>--_size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--_size<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>--_color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">rotate</span><span class="token punctuation">:</span> -45deg<span class="token punctuation">;</span> <span class="token property">pointer-events</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.heart::after, .heart::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>--_color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</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">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--_size<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">var</span><span class="token punctuation">(</span>--_size<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">top</span><span class="token punctuation">:</span> 0px<span class="token punctuation">;</span> <span class="token property">left</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>--_size<span class="token punctuation">)</span> / 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.heart::before</span> <span class="token punctuation">{</span> <span class="token property">top</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>--_size<span class="token punctuation">)</span> / -2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Let’s make that new custom property <code>--left</code> work, Let’s update our hearts loop to set that custom property inline of each heart between a range of <code>0%</code> and <code>100%</code>:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript">$<span class="token punctuation">{</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>amount<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">&#39;heart&#39;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">index</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> randomNumberLeft <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">101</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class=&quot;heart&quot; data-item=&quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>index<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot; style=&quot;--left: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomNumberLeft<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%;&quot;&gt;&lt;/div&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span> </code></pre> <p>Great! We now have a bunch of hearts sticking at the top of our screen:</p> <picture> <source srcset="/_astro/sticky-hearts.Bzn6ZkaG_Z1v9Mwu.avif 320w, /_astro/sticky-hearts.Bzn6ZkaG_ZptkvQ.avif 480w, /_astro/sticky-hearts.Bzn6ZkaG_Z2mhpoU.avif 800w, /_astro/sticky-hearts.Bzn6ZkaG_1Jrq1e.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sticky-hearts.Bzn6ZkaG_Z6sbD1.webp 320w, /_astro/sticky-hearts.Bzn6ZkaG_YdgmC.webp 480w, /_astro/sticky-hearts.Bzn6ZkaG_ZWzNvr.webp 800w, /_astro/sticky-hearts.Bzn6ZkaG_Z1V36Te.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sticky-hearts.Bzn6ZkaG_Dkbbm.png" srcset="/_astro/sticky-hearts.Bzn6ZkaG_ZN90XT.png 320w, /_astro/sticky-hearts.Bzn6ZkaG_hwr1J.png 480w, /_astro/sticky-hearts.Bzn6ZkaG_Z1EgCQk.png 800w, /_astro/sticky-hearts.Bzn6ZkaG_2rscyO.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="All hearts are visible in the browser window, but unortunatly sticking to the top." loading="lazy" decoding="async" fetchpriority="auto" width="70" height="33"> </picture> <p>All that’s left for us to do is add a bit of animation. However, they are not perfectly optimized yet as I’m still using a margin for the swaying (which doesn’t make use of the GPU). I might update that later on, but for now, this is how I made the animation:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> goToTopHeart</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 100vh<span class="token punctuation">;</span> <span class="token property">scale</span><span class="token punctuation">:</span> .2<span class="token punctuation">;</span> <span class="token property">margin-left</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>--_sway<span class="token punctuation">)</span> * 1px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">10%</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 punctuation">}</span> <span class="token selector">18%</span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">10%, 30%, 50%, 70%, 90%</span> <span class="token punctuation">{</span> <span class="token property">margin-left</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>--_sway<span class="token punctuation">)</span> * -1px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">20%, 40%, 60%, 80%, 100%</span> <span class="token punctuation">{</span> <span class="token property">margin-left</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>--_sway<span class="token punctuation">)</span> * 1px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">80%</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">scale</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 -100%<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">scale</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>And let’s now initialize the animation on our hearts, once again making use of those custom properties that we’ve set in our <code>:host</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.heart</span> <span class="token punctuation">{</span> <span class="token comment">/* previous code */</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> goToTopHeart <span class="token function">var</span><span class="token punctuation">(</span>--_duration<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--_iteration<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--_ease<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-delay</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>--delay<span class="token punctuation">)</span> * .1s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Notice that we’ve set a delay, we will be needing this later on. Because - for now - our hearts look like this:</p> <video width="320" height="240" controls="" preload="metadata" aria-label="All hearts animate together to the top"><source src="/_astro/all-together.0vQd2dk2.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>All that is left for us to do is to add a delay for each heart, let’s do that by overwriting that custom property inside of our loop, the full <code>html</code> will look like this:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> &lt;div class=&quot;for-love-hearts&quot; part=&quot;hearts-container&quot;&gt; </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>amount<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token string">&#39;heart&#39;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">index</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> randomNumberLeft <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">101</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> randomNumberDelay <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">101</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> html<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;div class=&quot;heart&quot; data-item=&quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>index<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot; style=&quot;--left: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomNumberLeft<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%; --delay: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomNumberDelay<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">;&quot;&gt;&lt;/div&gt;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> &lt;/div&gt; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> </code></pre> <h3 id="consider-user-preferences">Consider user preferences</h3> <p>Keep in mind that there are users out there that don’t want to have any unintentional animations on the screen. Yes, it would make this web component a disturbing factor, but better that, than an unhappy user. Let’s disable animation and hide the container for users that set their preferences to reduced motion by adding the following to our CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>prefers-reduced-motion<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.heart, .for-love-hearts</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 property">animation</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>All of these things together result in the following:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="for-love-element basic" src="https://codepen.io/utilitybend/embed/preview/poYJZzr?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/poYJZzr"> for-love-element basic</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="a-lovely-little-experiment">A lovely little experiment…</h2> <p>If you’d like to take a bit of a deeper dive inside of this web component, here are a few links:</p> <ul> <li> <a href="https://github.com/brechtDR/for-love-element" target="_blank" rel="noreferrer noopener">For-love-element on GitHub</a> </li> <li> <a href="https://www.npmjs.com/package/for-love-element" target="_blank" rel="noreferrer noopener">for-love-element on npm</a> </li> <li>Previous article “<a href="https://utilitybend.com/blog/getting-into-web-components-an-intro">Getting into web components</a>” with some handy links for those that want to learn more about them</li> </ul> <p>Although I’m absolutely sure that this web component is useless and not production-ready because it could use some optimising, I’m very pleased to have created my first web component. I do think it could solve a lot of the tedious tasks we have as front-end developers and I do believe they deserve a bit more attention in 2024.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="for-love-element multicolor" src="https://codepen.io/utilitybend/embed/preview/RwdPBwb?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/RwdPBwb"> for-love-element multicolor</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div>Brecht De RuyteGetting into web components - an introhttps://utilitybend.com/blog/getting-into-web-components-an-intro/https://utilitybend.com/blog/getting-into-web-components-an-intro/The year 2024 might be a big one for web components and I for one am excited about this. Truth be told - a few months ago - I barely knew anything about them as I thought they were just this niche thing floating around on the web. But a lot of buzz is being created. So I wanted to learn more about them. I can now proudly say I published my first (useless) web component on npm - a Valentines special - for-love element.Mon, 05 Feb 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-webcomponents.Bb3mLcAr_2rMPz.avif 375w, /_astro/visual-webcomponents.Bb3mLcAr_22xoSw.avif 480w, /_astro/visual-webcomponents.Bb3mLcAr_Z6nuJ4.avif 680w, /_astro/visual-webcomponents.Bb3mLcAr_7NPj9.avif 800w, /_astro/visual-webcomponents.Bb3mLcAr_ZRgMKz.avif 980w, /_astro/visual-webcomponents.Bb3mLcAr_Zi1zCR.avif 1024w, /_astro/visual-webcomponents.Bb3mLcAr_Z1tz9fM.avif 1660w, /_astro/visual-webcomponents.Bb3mLcAr_165jVq.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-webcomponents.Bb3mLcAr_Z1TYYzw.webp 375w, /_astro/visual-webcomponents.Bb3mLcAr_55Bsq.webp 480w, /_astro/visual-webcomponents.Bb3mLcAr_Z23Piaa.webp 680w, /_astro/visual-webcomponents.Bb3mLcAr_Z1OCW6W.webp 800w, /_astro/visual-webcomponents.Bb3mLcAr_2fsxCg.webp 980w, /_astro/visual-webcomponents.Bb3mLcAr_1ewUi.webp 1024w, /_astro/visual-webcomponents.Bb3mLcAr_Z1aj1GC.webp 1660w, /_astro/visual-webcomponents.Bb3mLcAr_HRRED.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-webcomponents.Bb3mLcAr_1cCvCu.jpg" srcset="/_astro/visual-webcomponents.Bb3mLcAr_23crFn.jpg 375w, /_astro/visual-webcomponents.Bb3mLcAr_Z11T55B.jpg 480w, /_astro/visual-webcomponents.Bb3mLcAr_1Tm95J.jpg 680w, /_astro/visual-webcomponents.Bb3mLcAr_28yu8W.jpg 800w, /_astro/visual-webcomponents.Bb3mLcAr_18sQ4e.jpg 980w, /_astro/visual-webcomponents.Bb3mLcAr_13LUlO.jpg 1024w, /_astro/visual-webcomponents.Bb3mLcAr_Z7KDg6.jpg 1660w, /_astro/visual-webcomponents.Bb3mLcAr_2uDUei.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="a-two-part-post-on-web-components">A two-part post on web components</h2> <p>This article will be split into two parts. In this first section, I’d like to cover the basics of web components, while the second part will serve as a tutorial on creating my first (useless) component.</p> <p>For those who are utterly clueless about web components - no need to feel ashamed; I’m catching up myself. In this article, I want to delve into the theory and some basic terminology that was complete gibberish to me until a few months ago. One of the key aspects of my blog is documenting the things I learn, with the hope that others can also benefit from it. While there might be better in-depth tutorials available on this subject, I’ll write about some basics and include some recommended reads at the end of this article.</p> <p>If you do not care about the basics and lingo and just want to hop over to the component I created, that’s fine.</p> <a href="https://github.com/brechtDR/for-love-element" target="_blank" class="btn btn-primary">The for-love-element on GitHub</a> <h2 id="what-are-web-components">What are web components?</h2> <p>One of my favorite weekend activities is opening a box of LEGO Classic with my daughter and simply building things—sometimes following the booklet, sometimes relying on our imagination. When we search through the LEGO boxes for pieces, we occasionally come across some that are still connected, making it easier for us to avoid starting everything from scratch. It’s perfect; you’ve found yourself a component that you can easily reuse.</p> <p>This is a perfect analogy for web components; they’re like building blocks for websites, or perhaps even groups of building blocks.</p> <p>I’m sure that this analogy has been made a lot in the past as I did a quick Google search and got a few hits, but it’s just perfect for it so I’m shamelessly re-using it here. (See what I did here? The analogy just became a component)</p> <h2 id="lets-start-with-the-lingo-custom-elements">Let’s start with the lingo: Custom Elements</h2> <p>Instead of sticking with standard tags like <code>&lt;div&gt;</code> or <code>&lt;button&gt;</code>, we can design your own tags, such as <code>&lt;my-button&gt;</code> or <code>&lt;for-love&gt;</code>. These tags represent unique components that we can use and reuse across our website or web app. It’s like having your own arsenal of tools perfected to your needs. Custom elements always need to have a hyphen inside of them to distinguish them from standard HTML elements and to follow the naming conventions set by the <a href="https://www.webcomponents.org/specs" target="_blank" rel="noreferrer noopener">Web Components specification</a>.</p> <p>The main reasoning behind the hyphen is to avoid conflicts - reducing the chance of naming conflicts with standard HTML elements. Since standard HTML elements don’t contain hyphens (and now of course, never will).</p> <h2 id="light-and-shadow-dom">Light and Shadow DOM</h2> <p>Here is where it got a bit complicated for me at first, but this is kinda the best way for me to learn/visualize it:</p> <p>The Shadow DOM is like a hidden room for web elements. Let’s say you have a workspace where you’re building different parts of a website. Some parts need to do their job without affecting the rest. The Shadow DOM provides an encapsulated space that does just that. It’s like putting a transparent curtain around a specific area. Elements inside this “hidden room” can have their own styles, scripts, and structure without interfering with the styles and scripts of the main page or other elements. Those other elements we’re referring to are what we call the Light DOM.</p> <p>I can’t remember where I heard that transparent curtain analogy, I didn’t invent it, but it’s the one that stuck with me. That and this simple example of a web component that has an output <em>“This is content inside the Shadow DOM”</em>:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>This is content outside the Shadow DOM, thus &quot;Light DOM&quot;.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-element</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-element</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> <span class="token comment">// Create a new custom element</span> <span class="token keyword">class</span> <span class="token class-name">MyElement</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create a Shadow DOM for this custom element</span> <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">&#39;open&#39;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create a paragraph inside the Shadow DOM</span> <span class="token keyword">const</span> paragraph <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">&#39;p&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> paragraph<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">&#39;This is content inside the Shadow DOM.&#39;</span><span class="token punctuation">;</span> <span class="token comment">// Append the paragraph to the Shadow DOM</span> shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>paragraph<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// Define our custom element</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">&#39;my-element&#39;</span><span class="token punctuation">,</span> MyElement<span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The Shadow DOM has some very specific characteristics:</p> <h3 id="encapsulation">Encapsulation:</h3> <p>Inside of the Shadow DOM, your styles and scripts are not affected by the outside world. This behavior helps to prevent your styles from accidentally messing with other elements on your webpage. Outside CSS won’t have any effect on it <strong>except</strong> for custom properties.</p> <h3 id="composition">Composition:</h3> <p>The Shadow DOM introduces slots, little placeholders where you can slide in your own content. This means you can customize the insides of your elements without messing with the original design. This is so powerful and let me tell you why:</p> <p>In the following example, We will slot an <code>&lt;h2&gt;</code> and <code>&lt;p&gt;</code> inside of a web component. The only thing that it will do is place an <code>&lt;hr&gt;</code> between them (I know, not something you would use web components for, but it’s a simple example).</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-card</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- Content placed inside the slots --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>title<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Card Title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>content<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>This is the content of the card.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-card</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">class</span> <span class="token class-name">MyCard</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create a Shadow DOM for this custom element</span> <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">&#39;open&#39;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create a container for the card content</span> <span class="token keyword">const</span> cardContainer <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">&#39;div&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create slots for title and content place hr between them</span> cardContainer<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> &lt;slot name=&quot;title&quot;&gt;&lt;/slot&gt; &lt;hr&gt; &lt;slot name=&quot;content&quot;&gt;&lt;/slot&gt; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token comment">// Append the card container to the Shadow DOM</span> shadow<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>cardContainer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">&#39;my-card&#39;</span><span class="token punctuation">,</span> MyCard<span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now imagine JavaScript fails, the page would still be showing the slotted content that is provided inside of the web component as this is just some light DOM and the only thing missing would be the <code>&lt;hr&gt;</code>. Even if you would have to support a browser that doesn’t support this, it could still be a progressive enhancement.</p> <h3 id="accessibility">Accessibility:</h3> <p>Accessibility features are a shared language. Elements inside the Shadow DOM can understand and speak the same accessibility language as elements outside. What’s even more is that it can really help us in some of the harder accessibility use cases such as tabs. A great collection of web components that does just that can be found at <a href="https://genericcomponents.netlify.app/" target="_blank" rel="noreferrer noopener">genericcomponents</a>.</p> <h3 id="separation-of-concerns">Separation of Concerns:</h3> <p>Shadow DOM lets you neatly arrange your structure, styles, and behavior in their own compartments. Imagine having a library of web components for all those repeating tasks: accessible tabs, image zooms, and animatable dropdowns. We can still use custom properties for styling because those will bleed through the web component. But if we want to do some serious theming and want to create flexible web components, the next one is probably even more interesting…</p> <h3 id="the-part-pseudo-element">The ::part pseudo element</h3> <p>It allows you to style specific named parts of a web component from outside its Shadow DOM.</p> <p>Here is an example of this. Let’s combine a few things; Imagine the following example:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-element</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>header<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Header Content<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">slot</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>content<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Main Content<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-element</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This is what our web component looks like (give extra attention to the attributes of the <code>innerHTML</code>):</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">class</span> <span class="token class-name">MyElement</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shadow <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">attachShadow</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">&quot;open&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shadow<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"> &lt;style&gt; div { border: 3px solid blue; } &lt;/style&gt; &lt;div part=&quot;header&quot;&gt;&lt;slot name=&quot;header&quot;&gt;&lt;/slot&gt;&lt;/div&gt; &lt;hr/&gt; &lt;div part=&quot;content&quot;&gt;&lt;slot name=&quot;content&quot;&gt;&lt;/slot&gt;&lt;/div&gt; </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">&quot;my-element&quot;</span><span class="token punctuation">,</span> MyElement<span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>Now the best way to explain this is to visualize it with borders. By default, the <code>&lt;div&gt;</code> element inside of our shadow DOM has a blue border. We want to give a green border to the <code>&lt;div&gt;</code> wrapping the <em>header slot</em>, we can access this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* style our default h2 */</span> <span class="token selector">h2</span> <span class="token punctuation">{</span> <span class="token property">margin-block</span><span class="token punctuation">:</span> 30px<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 property">border</span><span class="token punctuation">:</span> 3px solid red<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* style our web component parts green border for header */</span> <span class="token selector">my-element::part(header)</span> <span class="token punctuation">{</span> <span class="token property">border</span><span class="token punctuation">:</span> 3px solid green<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Here is that in a little CodePen:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Web components part demo for article " src="https://codepen.io/utilitybend/embed/preview/LYazZeg?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/LYazZeg"> Web components part demo for article </a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="the-developer-experience-of-web-components">The developer experience of web components</h2> <p>I’ve shown you some basic examples and it’s time to be honest… Writing webcomponents like this isn’t a great developer experience… at least in my opinion. It seems to work fine for basic examples, but I just love a bit of structure and colors in my editor instead of just throwing every output into a string.</p> <p>Inspired by Lucien Immink, a colleague of mine who has been touring with a presentation stating that a little library called “Lit” is on fire. I decided to give it a go… and yes! It was a lot more fun to write a little web component. Thank you Lucien for the hot tip.</p> <h2 id="lit-using-a-library-to-help-create-web-components">Lit: Using a library to help create web components</h2> <p>The <a href="https://lit.dev/" target="_blank" rel="noreferrer noopener">Lit library</a> is designed to make it easier and more efficient to work with web components. It offers several advantages that can ease some of the burdens by just writing them the normal way.</p> <p>Lit provides a more declarative syntax for defining components. Using this approach can make our code easier to understand and manage, as opposed to the more complex style needed when dealing directly with the Web Components API. It also has this neat templating system that combines HTML and JavaScript template literals, making it more convenient to work with dynamic content. This not only helps with some nice colors in our editor but also helps with their updating strategy that only updates the parts of the DOM that have changed. This can lead to improved performance compared to manual updates.</p> <p>With all of these things combined, I’d say it’s worth taking a look at it if you’re serious about web components as this library is only about 6kb in size.</p> <p>There is a lot more going on such as experimental server-side rendering, but I’m just starting with this and will need to get into the details a bit more myself.</p> <p>To give an example, this is the same border demo, but then in Lit</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Lit example for article" src="https://codepen.io/utilitybend/embed/preview/yLwPQjJ?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/yLwPQjJ"> Lit example for article</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="final-thoughts-and-demo">Final thoughts and demo</h2> <p>I’ve just gotten into this whole web component thing and I really like what I see. I’m so late to the party and it has been on my list for a while. I love the web platform and because I keep a high interest in <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">Open UI</a>, I was inspired to take a deeper dive into this kind of thing.</p> <p>I love the philosophy behind web components and believe that it’s an important thing to get into. As companies grow bigger, so does the need for different frameworks, which makes web components a perfect thing to integrate in your company’s ever evolving tech stack.</p> <p>So many people are talking about them lately and I’m just giving it a go. I found the basics and lingo hard at first, so I hope that writing this article this could clear up some of the fog for others sharing the same struggle.</p> <p><strong>Here are a few reads that I enjoyed:</strong></p> <ul> <li><a href="https://adactio.com/journal/20618" target="_blank" rel="noreferrer noopener">HTML web components</a> by Jeremy Keith</li> <li><a href="https://www.oddbird.net/2023/11/17/components/" target="_blank" rel="noreferrer noopener">HTML Web Components are Just JavaScript?</a> by Miriam Suzanne</li> <li><a href="https://jakelazaroff.com/words/web-components-will-outlive-your-javascript-framework/" target="_blank" rel="noreferrer noopener">Web Components Will Outlive Your JavaScript Framework</a> by Jake Lazaroff</li> <li><a href="https://techhub.iodigital.com/articles/why-Lit-is-on-fire" target="_blank" rel="noreferrer noopener">Why Lit is on fire</a> by Lucien Imminck</li> <li><a href="https://kinsta.com/blog/web-components/" target="_blank" rel="noreferrer noopener">A Complete Introduction to Web Components</a> by Craig Buckler</li> </ul> <p>And also a special shoutout to the <a href="https://frontendmasters.com/courses/web-components/" target="_blank" rel="noreferrer noopener">Frontend masters course on web components by Dave Rupert</a>. Yes, it is a lesson you need to buy, but it helped me to get a start on the matter. In the next article, I will be doing a little breakdown of how I created my first (useless) web component.</p> <p><em>Yes, it’s useless <br>Yes, It might not be the best example out there <br>Yes, it’s an npm package <br>Yes, I am so proud of it because “I did a thing”</em></p> <a href="https://www.npmjs.com/package/for-love-element" target="_blank" rel="noreferrer noopener">You can find the for-love element with info here</a> <p>And an example right here at the end of this article. Next article I will do a step by step of the creation of it.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="for-love-element basic" src="https://codepen.io/utilitybend/embed/preview/poYJZzr?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/poYJZzr"> for-love-element basic</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div>Brecht De RuyteGoing beyond pixels and (r)ems in CSShttps://utilitybend.com/blog/going-beyond-pixels-and-rems-in-css/https://utilitybend.com/blog/going-beyond-pixels-and-rems-in-css/There are a lot of CSS units available at the moment and we mostly still rely on pixels and (r)ems for our sizing and fonts. I say it’s time to do a little freshening up. In this mini-series I will be going around all the current length units in CSS with practical examples.Tue, 30 Jan 2024 00:00:00 GMT<picture> <source srcset="/_astro/io.Dg3cHcwk_Z25b8NH.avif 375w, /_astro/io.Dg3cHcwk_tOkKt.avif 480w, /_astro/io.Dg3cHcwk_Z5wM08.avif 680w, /_astro/io.Dg3cHcwk_todc8.avif 800w, /_astro/io.Dg3cHcwk_ZAfldy.avif 980w, /_astro/io.Dg3cHcwk_ZmaGMW.avif 1024w, /_astro/io.Dg3cHcwk_20jHNz.avif 1660w, /_astro/io.Dg3cHcwk_1Wjfzb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/io.Dg3cHcwk_Z1Ab5it.webp 375w, /_astro/io.Dg3cHcwk_XOogH.webp 480w, /_astro/io.Dg3cHcwk_osgv6.webp 680w, /_astro/io.Dg3cHcwk_XogHm.webp 800w, /_astro/io.Dg3cHcwk_Z6fhHk.webp 980w, /_astro/io.Dg3cHcwk_ZmJlnD.webp 1024w, /_astro/io.Dg3cHcwk_1YK4dS.webp 1660w, /_astro/io.Dg3cHcwk_1EH8qP.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/io.Dg3cHcwk_VsBpc.jpg" srcset="/_astro/io.Dg3cHcwk_16WAjf.jpg 375w, /_astro/io.Dg3cHcwk_Z1oe3Uv.jpg 480w, /_astro/io.Dg3cHcwk_Z1XAbG7.jpg 680w, /_astro/io.Dg3cHcwk_Z1oEbtQ.jpg 800w, /_astro/io.Dg3cHcwk_Z2tiJTx.jpg 980w, /_astro/io.Dg3cHcwk_2bttFL.jpg 1024w, /_astro/io.Dg3cHcwk_ZwdevD.jpg 1660w, /_astro/io.Dg3cHcwk_Zirdpp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="iO tech_hub" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://techhub.iodigital.com/articles/going-beyond-pixels-and-rems-in-css/relative-length-units-based-on-font" target="_blank">Read the article at iO tech_hub</a></div>Brecht De RuyteTaking a closer look at @property in CSShttps://utilitybend.com/blog/taking-a-closer-look-at-property-in-css/https://utilitybend.com/blog/taking-a-closer-look-at-property-in-css/Browser support for @property nears completion, a great time to delve into some technical aspects of this CSS feature. While I've experimented with @property in demo settings before, I’ve never used it in a production environment. With the upcoming full browser support, let’s take a look at the inner workings and uncover some of the benefits.Mon, 08 Jan 2024 00:00:00 GMT<picture> <source srcset="/_astro/visual-at-property.DeIZmlOS_12k5xQ.avif 375w, /_astro/visual-at-property.DeIZmlOS_1AgafT.avif 480w, /_astro/visual-at-property.DeIZmlOS_Z2piwEN.avif 680w, /_astro/visual-at-property.DeIZmlOS_Z1DW57J.avif 800w, /_astro/visual-at-property.DeIZmlOS_Z1161j4.avif 980w, /_astro/visual-at-property.DeIZmlOS_ZaiubO.avif 1024w, /_astro/visual-at-property.DeIZmlOS_1i29Uz.avif 1660w, /_astro/visual-at-property.DeIZmlOS_ZyCvJU.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-at-property.DeIZmlOS_2aUycN.webp 375w, /_astro/visual-at-property.DeIZmlOS_Z2lkuT5.webp 480w, /_astro/visual-at-property.DeIZmlOS_Z1gH40Q.webp 680w, /_astro/visual-at-property.DeIZmlOS_ZvlBsM.webp 800w, /_astro/visual-at-property.DeIZmlOS_7urkS.webp 980w, /_astro/visual-at-property.DeIZmlOS_Zo9VBS.webp 1024w, /_astro/visual-at-property.DeIZmlOS_14aHuv.webp 1660w, /_astro/visual-at-property.DeIZmlOS_Z2w5jb1.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-at-property.DeIZmlOS_Cxlqi.jpg" srcset="/_astro/visual-at-property.DeIZmlOS_Z1Qqgnc.jpg 375w, /_astro/visual-at-property.DeIZmlOS_Z1iubF9.jpg 480w, /_astro/visual-at-property.DeIZmlOS_ZdQJLU.jpg 680w, /_astro/visual-at-property.DeIZmlOS_wtGK9.jpg 800w, /_astro/visual-at-property.DeIZmlOS_1akKyO.jpg 980w, /_astro/visual-at-property.DeIZmlOS_1wHWxt.jpg 1024w, /_astro/visual-at-property.DeIZmlOS_Z258w94.jpg 1660w, /_astro/visual-at-property.DeIZmlOS_1r784S.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>Without going too much into the history about this technique of handling our custom properties, it might be nice to repeat just a little of the basics for those landing here and having absolutely no clue what I’m going on about.</p> <h2 id="what-is-this-property-thing">What is this @property thing?</h2> <p>The <code>@property</code> rule is part of the CSS Houdini umbrella of APIs, which aims to provide more control and flexibility for authors. It was introduced in the CSS Properties and Values API Level 1 spec around 2019 and has had a long support in Chrome and Edge, followed up by Safari. I personally didn’t experiment that much with Houdini when it first came around as it never felt natural to me.</p> <h3 id="the-purpose-of-property">The Purpose of @property</h3> <p>The <code>@property</code> rule allows developers to explicitly define custom CSS properties (also known as CSS variables). This provides several benefits:</p> <ul> <li><strong>Property type checking and constraining</strong>: We can specify the data type of a custom property, such as <code>&lt;number&gt;</code>, <code>&lt;color&gt;</code>, or <code>&lt;length&gt;</code>. This helps prevent errors and ensures that custom properties are used correctly, a win for design systems and we all love typing these days, right? <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax" target="_blank" rel="noreferrer noopener">Find the full list of syntax types here</a></li> <li><strong>Setting default values</strong>: We can set a default value for a custom property declared this way with the <code>initial-value</code> property. This ensures that the property has a fallback when not set elsewhere in the stylesheet.</li> <li><strong>Defining inheritance behavior:</strong> We get to choose whether a custom property can inherit values from its parent elements or not. That way we can manage how our custom properties are applied in the cascade, more about that later on.</li> </ul> <h3 id="basics-of-property">Basics of @property</h3> <p>The <code>@property</code> rule gives us a more structured and powerful way to create and use custom properties. This opens up new possibilities for customization and especially theming. Here’s an example of how to define a custom property named <code>--primary-color</code> with a <code>initial-value</code> of <code>#ea1ca5</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --primary-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;color&gt;&quot;</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> #ea1ca5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This definition specifies that <code>--primary-color</code> is a color property, it can inherit values from parent elements, and its default value is <code>#ea1ca5</code>.</p> <p>Once a custom property is defined, it can be used in CSS declarations just like any other property:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">body</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>--primary-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This declaration sets the <code>background-color</code> of the body element to the value of the <code>--primary-color</code> custom property.</p> <p>Now that we got the basics right, off to the fun stuff! Let’s talk about the opportunities this opens.</p> <h2 id="property-type-checking-and-constraining">Property type checking and constraining</h2> <p>By using <code>@property</code> to define custom properties with clear types, we can easily maintain and update our code. This can make it less time-consuming and error-safe to modify custom properties as a project evolves. It’s even a bigger win when building a design system that gets used by third-parties, showing the users that something doesn’t seem right in DevTools.</p> <p>Just as the first example, let create a little playground with color:</p> <p>At the top of our CSS we will add the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;color&gt;&quot;</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> #ea1ca5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Our playground HTML will contain three div elements, each with a different class:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>color-1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>color-2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>color-3<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now let’s set the default custom property on the <code>&lt;div&gt;</code> element, give it some width and height. Next up, we’ll overwrite this custom property for each of the classes:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">div</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> center<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 20vw<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 100px<span class="token punctuation">;</span> <span class="token property">background</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 selector">.color-1</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #1a535c<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-2</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> <span class="token function">rgb</span><span class="token punctuation">(</span>100<span class="token punctuation">,</span> 200<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.color-3</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> #3e47db<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>With a bit of extra styling, this is the result.</p> <picture> <source srcset="/_astro/color-right.DZRQzBZd_EhBpo.avif 320w, /_astro/color-right.DZRQzBZd_27D16F.avif 480w, /_astro/color-right.DZRQzBZd_1nY99K.avif 800w, /_astro/color-right.DZRQzBZd_2mSraI.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/color-right.DZRQzBZd_WHnVc.webp 320w, /_astro/color-right.DZRQzBZd_2q3MCt.webp 480w, /_astro/color-right.DZRQzBZd_1GoUFy.webp 800w, /_astro/color-right.DZRQzBZd_Z2oRU7p.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/color-right.DZRQzBZd_lQgBS.png" srcset="/_astro/color-right.DZRQzBZd_Z1dVDzf.png 320w, /_astro/color-right.DZRQzBZd_eoK72.png 480w, /_astro/color-right.DZRQzBZd_Zuf6OS.png 800w, /_astro/color-right.DZRQzBZd_tEbb5.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="The colors are all shown perfectly, no matter the color function" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <h3 id="what-happened-here">What happened here?</h3> <p>Notice that it doesn’t really matter how we write the colors when using the <code>&lt;color&gt;</code> type. For example, in <code>.color-2</code>, we chose to use the <code>rgb()</code> function and it still works.</p> <p>So all of this is valid, but let’s take a look at what happens when we change <code>.color-1</code> to an integer.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.color-1</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> 4<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <picture> <source srcset="/_astro/color-fallback.DIY8KBbO_2avegD.avif 320w, /_astro/color-fallback.DIY8KBbO_1N4vPJ.avif 480w, /_astro/color-fallback.DIY8KBbO_2u2yto.avif 800w, /_astro/color-fallback.DIY8KBbO_2r0P5H.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/color-fallback.DIY8KBbO_Z1uYiDO.webp 320w, /_astro/color-fallback.DIY8KBbO_Z1Rq14I.webp 480w, /_astro/color-fallback.DIY8KBbO_Z1brXr4.webp 800w, /_astro/color-fallback.DIY8KBbO_Z1etGOK.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/color-fallback.DIY8KBbO_2og3Dn.png" srcset="/_astro/color-fallback.DIY8KBbO_Z2cF7YH.png 320w, /_astro/color-fallback.DIY8KBbO_2v5iok.png 480w, /_astro/color-fallback.DIY8KBbO_Z1S8MLW.png 800w, /_astro/color-fallback.DIY8KBbO_Z1VawaD.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="Because of the wrong syntax for a color, the fallback is shown in color-1" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>We are now seeing our fallback color as the value is invalid. It’s handy to have a fallback system like this and personally, I love this.</p> <h3 id="debugging-property-in-chrome-devtools">Debugging @property in Chrome DevTools</h3> <p>With Chrome version 118, DevTools provides useful information about typed custom properties. Let’s inspect the element with an invalid color value after setting the <code>--color</code> property to <code>4</code>. A warning icon appears next to the custom property. Hovering over the icon reveals an error message stating that the property value is invalid and the expected type is <code>&lt;color&gt;</code>.</p> <picture> <source srcset="/_astro/devtools-at-property-info.ClxFBDjP_Zp0Pa9.avif 320w, /_astro/devtools-at-property-info.ClxFBDjP_1JN6KP.avif 480w, /_astro/devtools-at-property-info.ClxFBDjP_2p5kik.avif 800w, /_astro/devtools-at-property-info.ClxFBDjP_Z1eW9oh.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/devtools-at-property-info.ClxFBDjP_adRiV.webp 320w, /_astro/devtools-at-property-info.ClxFBDjP_2k2OeU.webp 480w, /_astro/devtools-at-property-info.ClxFBDjP_Z25R62w.webp 800w, /_astro/devtools-at-property-info.ClxFBDjP_ZEHqUc.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/devtools-at-property-info.ClxFBDjP_1nrQQT.png" srcset="/_astro/devtools-at-property-info.ClxFBDjP_TJeB7.png 320w, /_astro/devtools-at-property-info.ClxFBDjP_Z20CWgP.png 480w, /_astro/devtools-at-property-info.ClxFBDjP_Z1llIJl.png 800w, /_astro/devtools-at-property-info.ClxFBDjP_4MUmY.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt=""DevTools" in="" Chrome="" CSS="" panel="" showing="" notice:="" Invalid="" property="" value="," expected="" type="" ="<color">" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>An anchor below the warning directs us to the custom property declaration.</p> <figure class="thirds image"><picture> <source srcset="/_astro/at-property-declaration.BR7nL01Y_ZEzJpc.avif 320w, /_astro/at-property-declaration.BR7nL01Y_Z1c7E2O.avif 480w, /_astro/at-property-declaration.BR7nL01Y_ZPm0OB.avif 800w, /_astro/at-property-declaration.BR7nL01Y_1cqVf.avif 980w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/at-property-declaration.BR7nL01Y_Z12MbFY.webp 320w, /_astro/at-property-declaration.BR7nL01Y_Z1zk6jB.webp 480w, /_astro/at-property-declaration.BR7nL01Y_Z1dys6o.webp 800w, /_astro/at-property-declaration.BR7nL01Y_Zm00kx.webp 980w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/at-property-declaration.BR7nL01Y_1qiwXI.png" srcset="/_astro/at-property-declaration.BR7nL01Y_1kvdAH.png 320w, /_astro/at-property-declaration.BR7nL01Y_MXiX5.png 480w, /_astro/at-property-declaration.BR7nL01Y_19IWbi.png 800w, /_astro/at-property-declaration.BR7nL01Y_21ioW9.png 980w" sizes="(max-width: 1000px) 100vw, 90vw" alt="DevTools in Chrome CSS panel showing the full @property information" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture></figure> <p>When hovering over a valid custom property, we receive detailed information about the property:</p> <ul> <li>The current value</li> <li>The expected syntax</li> <li>The inherits boolean</li> <li>The initial value</li> <li>And an anchor to the declaration</li> </ul> <figure class="thirds image"><picture> <source srcset="/_astro/devtools-property-info.58DYoODd_ZmBxL8.avif 320w, /_astro/devtools-property-info.58DYoODd_Z22oyN3.avif 480w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/devtools-property-info.58DYoODd_Z2k4lce.webp 320w, /_astro/devtools-property-info.58DYoODd_15kLzM.webp 480w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/devtools-property-info.58DYoODd_Z9BKkm.png" srcset="/_astro/devtools-property-info.58DYoODd_1EsaRK.png 320w, /_astro/devtools-property-info.58DYoODd_ZjP9a.png 480w" sizes="(max-width: 1000px) 100vw, 90vw" alt="DevTools in Chrome, hovering over a custom property shows the current value and registered property info" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture></figure> <p>This DevTools functionality significantly enhances the efficiency of using typed custom properties.</p> <p><a href="https://youtu.be/-dOYxJnRhxQ?si=P6lu4h4aH8PWCPut&t=12" target="_blank" rel="noreferrer noopener">See it in action in this video by the DevTools team</a></p> <h2 id="defining-inheritance-behavior">Defining inheritance behavior</h2> <p>If the <code>inherits</code> property is set to <code>true</code>, the custom property value can be inherited by child elements. This means that if we set a value for the custom property on a parent element, that value will be applied to all of its child elements unless they have their own value set for the property. This is also the default for custom properties.</p> <p>Let’s keep using our colors demo for this and add a new element inside of the div with class <code>.color-2</code>. This is now the updated HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>color-1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>color-2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>inner<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>color-3<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now let’s give this new <code>div.inner</code> a bit of styling. To see our element clearly, we’re going to give this a white border as well.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">.inner</span> <span class="token punctuation">{</span> <span class="token property">background</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 property">border</span><span class="token punctuation">:</span> 3px solid white<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 50px<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> <p>We are making it inherit the <code>--color</code> custom property for its background, this will result to the following:</p> <picture> <source srcset="/_astro/inherit-true.B0zNI0R-_Z8N3PC.avif 320w, /_astro/inherit-true.B0zNI0R-_Z2wthsY.avif 480w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/inherit-true.B0zNI0R-_ZW6cjA.webp 320w, /_astro/inherit-true.B0zNI0R-_1JpHQY.webp 480w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/inherit-true.B0zNI0R-_Z1nmzWH.png" srcset="/_astro/inherit-true.B0zNI0R-_2jkx9L.png 320w, /_astro/inherit-true.B0zNI0R-_Z4kFsA.png 480w" sizes="(max-width: 1000px) 100vw, 90vw" alt="The inner div shows the same color as its parent, because inherit is set to true" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>Now let’s update our declaration of the custom property and set the <code>inherits</code> to <code>false</code>.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;color&gt;&quot;</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> #ea1ca5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The result:</p> <picture> <source srcset="/_astro/inherit-false.Bdqd-GVd_22sEEk.avif 320w, /_astro/inherit-false.Bdqd-GVd_2fJqGz.avif 480w" type="image/avif" sizes="(max-width: 1000px) 100vw, 90vw"><source srcset="/_astro/inherit-false.Bdqd-GVd_2pf521.webp 320w, /_astro/inherit-false.Bdqd-GVd_Z2rFhJF.webp 480w" type="image/webp" sizes="(max-width: 1000px) 100vw, 90vw"> <img src="https://utilitybend.com/_astro/inherit-false.Bdqd-GVd_2gVfv4.png" srcset="/_astro/inherit-false.Bdqd-GVd_23Qizv.png 320w, /_astro/inherit-false.Bdqd-GVd_2h84BK.png 480w" sizes="(max-width: 1000px) 100vw, 90vw" alt="The inner div shows the fallback / initial-value color, because inherit is set to false" loading="lazy" decoding="async" fetchpriority="auto" width="160" height="90"> </picture> <p>Because the <code>inherits</code> property is set to <code>false</code>, the custom property value cannot be inherited by child elements. This means that each child element must explicitly set its own value for the property, thus in this case, showing the <code>initial-value</code> color.</p> <h3 id="why-set-inherits-to-false">Why set inherits to false?</h3> <p>Disabling inheritance for custom properties could be a good practice when we need to have more control over the styling of individual elements and avoid unintended overrides (for example, in design systems or web components). It can also simplify our CSS code and make it more maintainable. However, if we would want to maintain a consistent style across a hierarchy of elements, inheritance can be a useful tool. This why I believe that setting inheritance to false should come with a sense of caution to avoid confusion when working on a large project with multiple authors. But that’s just my 2 cents, and writing documentation can go a long way.</p> <h2 id="unlocking-new-possibilities-with-property">Unlocking new possibilities with @property</h2> <p>The introduction of <code>@property</code> opened up exciting possibilities for CSS, particularly in the realm of animations. One of the most popular demonstrations of <code>@property</code> is the animation of a hue rotation wheel.</p> <p>Consider the following example:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> 0deg<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">background</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> 50%<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span></span> <span class="token punctuation">{</span> <span class="token selector">from</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> 0deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">to</span> <span class="token punctuation">{</span> <span class="token property">--hue</span><span class="token punctuation">:</span> 360deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p><strong>This won’t work at all</strong>, because the browser has no idea on how to animate hue rotation. However, using <code>@property</code>, we can explicitly instruct the browser to interpret the <code>--hue</code> variable as an <code>&lt;angle&gt;</code> value, enabling smooth animation:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --hue</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;angle&gt;&quot;</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> 0deg<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Hue wheel rotate" src="https://codepen.io/utilitybend/embed/preview/KKGMmOy/93458983187eeffbac7d51e5455a878f?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/KKGMmOy/93458983187eeffbac7d51e5455a878f"> Hue wheel rotate</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Here is another example using that same technique in combination with some SVG:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="iO Style yours challenge" src="https://codepen.io/utilitybend/embed/preview/vYQYNEL?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/vYQYNEL"> iO Style yours challenge</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="animating-clip-path-with-custom-properties">Animating clip-path with custom properties</h3> <p>For a scroll driven animations demo I wanted a dotted line to appear in a timeline. To initially hide that line, animating a <code>clip-path</code> seemed like good idea, but we can’t just animate the percentages from a <code>clip-path</code> with a custom property. <code>@property</code> however, can be used to animate the dimensions of a clipping mask. For instance, to gradually reveal an element, we can define variables for the vertical and horizontal percentages of the clipping mask and animate them.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --clip-vertical</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</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> 95%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --clip-horizontal</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;percentage&gt;&quot;</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> 100%<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">clip-path</span><span class="token punctuation">:</span> <span class="token function">inset</span><span class="token punctuation">(</span>0 0 <span class="token function">var</span><span class="token punctuation">(</span>--clip-vertical<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-horizontal<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>We can set the initial value to (almost) completely hide the element with a full <code>clip-path</code>. To animate the removal of the clip-path, we can create an animation:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.item</span> <span class="token punctuation">{</span> <span class="token property">clip-path</span><span class="token punctuation">:</span> <span class="token function">inset</span><span class="token punctuation">(</span>0 0 <span class="token function">var</span><span class="token punctuation">(</span>--clip-vertical<span class="token punctuation">)</span> <span class="token function">var</span><span class="token punctuation">(</span>--clip-horizontal<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> showLine linear both<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> showLine</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">--clip-horizontal</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">--clip-vertical</span><span class="token punctuation">:</span> 95%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">60%</span> <span class="token punctuation">{</span> <span class="token property">--clip-horizontal</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--clip-vertical</span><span class="token punctuation">:</span> 95%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">--clip-horizontal</span><span class="token punctuation">:</span> 0%<span class="token punctuation">;</span> <span class="token property">--clip-vertical</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>This example can be found in the following demo (In combination with Scroll Driven Animations):</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Scroll driven animation timeline" src="https://codepen.io/utilitybend/embed/preview/dyQmQYy?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/dyQmQYy"> Scroll driven animation timeline</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h3 id="animating-background-image-gradients">Animating Background-Image Gradients</h3> <p>Animating the colors of a <code>background-image</code> gradient is unfortunately something that isn’t possible by default. However, by defining the gradient colors using <code>@property</code>, we can finally do this:</p> <p>By animating the <code>--from-color</code> and <code>--to-color</code> variables, we can create visually appealing transitions between gradient colors, This would be that basic idea:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@property</span> --from-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;color&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> #f0f<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 punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --to-color</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">&quot;&lt;color&gt;&quot;</span><span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> #0ff<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 punctuation">}</span> <span class="token selector">.item</span> <span class="token punctuation">{</span> <span class="token property">background</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> <span class="token function">var</span><span class="token punctuation">(</span>--from-color<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--to-color<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> gradient 4s linear infinite<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> gradient</span> <span class="token punctuation">{</span> <span class="token selector">0%, 100%</span> <span class="token punctuation">{</span> <span class="token property">--from-color</span><span class="token punctuation">:</span> #f0f<span class="token punctuation">;</span> <span class="token property">--to-color</span><span class="token punctuation">:</span> #0ff<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">--from-color</span><span class="token punctuation">:</span> #00f<span class="token punctuation">;</span> <span class="token property">--to-color</span><span class="token punctuation">:</span> #ff0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Here is that in action:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Animating Background-Image Gradients with @property" src="https://codepen.io/utilitybend/embed/preview/GReKZMN?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/GReKZMN"> Animating Background-Image Gradients with @property</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <p>Or take a look at the following example where I animate another gradient (with kudos to <a href="https://gradient.style/" target="_blank" rel="noreferrer noopener">gradient.style</a>)</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Animated Gradient BG with @property" src="https://codepen.io/utilitybend/embed/preview/NWJKqYX?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/NWJKqYX"> Animated Gradient BG with @property</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="it-is-a-game-changer">It is a game changer</h2> <p>This is really going to be a fantastic addition for those of us that love creating design systems and fancy animations. Remember that these are just some basic examples. I’ve seen quite a few examples out there by far greater minds than myself where even <a href="https://youtu.be/ZuZizqDF4q8?si=CzM5fWtpEitpXyY3&t=2446" target="_blank" rel="noreferrer noopener">numbers were converted to integers</a>. Really looking forward to what this will bring, whether that is for animations or just really clever typed design systems.</p> <p>I really went the extra mile with writing this as I hope that for some this can be a great entry point. To be honest, this was an entry point for myself as I was doing research while writing this article. Not bad for a first article of the year, not bad at all….</p> <p>Let 2024 bring you lots of happiness, love and beautiful splashes of CSS once again. May you be joyful, playful and keep on coding the amazing things you do.</p>Brecht De RuyteHappy holidays and a popping start of 2024!https://utilitybend.com/blog/happy-holidays-and-a-popping-start-of-2024/https://utilitybend.com/blog/happy-holidays-and-a-popping-start-of-2024/This year has been a crazy ride, from my first appearances in public speaking to becoming a Google Developer Expert and speaking in Berlin. It’s time to reflect a little on the past year, write a little recap and have some time with family and friends to kick-start the year of 2024.Thu, 21 Dec 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual-fireworks.G7rTPavZ_Z1ybUjR.avif 375w, /_astro/visual-fireworks.G7rTPavZ_ZSyVYV.avif 480w, /_astro/visual-fireworks.G7rTPavZ_Zk2X4Y.avif 680w, /_astro/visual-fireworks.G7rTPavZ_ZhS0Qz.avif 800w, /_astro/visual-fireworks.G7rTPavZ_Z17kJVF.avif 980w, /_astro/visual-fireworks.G7rTPavZ_2jeDYq.avif 1024w, /_astro/visual-fireworks.G7rTPavZ_Z1l24BL.avif 1660w, /_astro/visual-fireworks.G7rTPavZ_1WncpE.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-fireworks.G7rTPavZ_8u09J.webp 375w, /_astro/visual-fireworks.G7rTPavZ_N6XtF.webp 480w, /_astro/visual-fireworks.G7rTPavZ_1mCWoC.webp 680w, /_astro/visual-fireworks.G7rTPavZ_1oMTC2.webp 800w, /_astro/visual-fireworks.G7rTPavZ_zlawV.webp 980w, /_astro/visual-fireworks.G7rTPavZ_ZUDdX3.webp 1024w, /_astro/visual-fireworks.G7rTPavZ_uhbeG.webp 1660w, /_astro/visual-fireworks.G7rTPavZ_Z1YdsJk.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-fireworks.G7rTPavZ_WEUOj.jpg" srcset="/_astro/visual-fireworks.G7rTPavZ_Zz16g7.jpg 375w, /_astro/visual-fireworks.G7rTPavZ_5AR3O.jpg 480w, /_astro/visual-fireworks.G7rTPavZ_E7PXL.jpg 680w, /_astro/visual-fireworks.G7rTPavZ_GhNcb.jpg 800w, /_astro/visual-fireworks.G7rTPavZ_Z89URU.jpg 980w, /_astro/visual-fireworks.G7rTPavZ_2nxdYc.jpg 1024w, /_astro/visual-fireworks.G7rTPavZ_Z1gIuC0.jpg 1660w, /_astro/visual-fireworks.G7rTPavZ_ZVn9vo.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Fireworks above skyline made with CSS" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>My gosh, has it been a year already. Time sure does fly when you’re having fun. About a year ago, I wrote my holidays blogpost with a few goals:</p> <ul> <li>Keep writing, do something related to scroll driven animations and CSS anchoring</li> <li>Return to CSS day</li> <li>Do some more guest writing</li> <li>Follow Open-UI a bit more closely</li> <li>Try public speaking</li> </ul> <p>If this were a checklist I’d say I pretty much checked all of those points. I wrote about 17 articles this year, including 3 guest articles for <a href="https://www.smashingmagazine.com/author/brecht-de-ruyte/" target="_blank" rel="noreferrer noopener">Smashing Magazine</a>. I haven’t written for CSS-Tricks anymore as they - unfortunately - don’t allow any guest writers anymore.</p> <p>But on a positive side, Geoff Graham started working at Smashing Magazine, so I was able to work with him again, which is just awesome.</p> <h2 id="w3c-community-groups">W3C community groups</h2> <p>I did keep following along with <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">Open-UI</a>. I still learn a ton out of this community group and I love the direction it is going. Now with invokers being available in Canary, exclusive accordions getting releases in multiple browsers and selectlist having a great prototype, it really is a joy to play around with all these goodies.</p> <p>What’s more is that there actually are releases for popover in Chrome, Edge and Safari. With the polyfill available, <strong>it’s going to be a popping 2024</strong>. I’ve also started following along with the CSS Next community group. But I’m still playing a bit of catch up.</p> <h2 id="keep-building-fresh-content-or-updates">Keep building fresh content or updates</h2> <p>I try to write fresh content, or at least approach something from a different angle and will continue to do so in 2024. This is also a journey for me and articles get created by exploring and learning new things around the web. It’s also just nice to look back at the things I’ve learned.</p> <p>I think it’s important to be true to yourself and others. When I write about a certain technique inspired by someone else, I will always mention the person who created it. When showing a demo by someone else while speaking, I add it to the credits and mention it out loud. I think that’s just basic respect and I hope people would do the same with the things I create. I’d rather write nothing, than to just steal something without shame.</p> <h2 id="public-speaking">Public speaking</h2> <p>From a first try-out at fullstack Belgium to speaking in Berlin as a GDE, If you would’ve told me that at the beginning of this year, I probably would’ve laughed at you. I’m still not the confident person that I want to be when doing this, but it’s gotten a bit better. I did learn that public speaking is something that can be trained, so when opportunities are presented for it, I will keep trying and working to get better (in my own “quirky” way).</p> <h2 id="a-word-of-thanks">A word of thanks</h2> <p>I’m grateful to a lot of people… Because this blog and the content I bring would be nothing without the patience, tutoring, retweets and invites by a lot of people.</p> <p><strong>It’s impossible to name everyone, but to summarize:</strong> Thank you, to everyone at the Open-UI group (Greg, Mason, Scott, Hidde, Luke and many more) for being patient with me and letting me help out, to the Google DevRel people (Una, Adam, Bramus) sharing my content and inspiring me, the people at Smashing Magazine (Iris, Geoff,…), to Dries and Freek for giving me my first chance to public speaking at Fullstack Belgium, to Claudia, for my first time speaking at Fronteers Belgium, all my colleagues at iO for the fun times, inspiration and hard coding sessions and the fantastic front-end developer day this year!</p> <p>And especially, thank you to my family, my wife and my daughter, I’d be lost without them.</p> <h2 id="goals-for-2024">Goals for 2024</h2> <p>This might sound a bit strange, but I’m pretty happy at the moment. I was thinking of trying something with video as well, but as I hate a camera in my face, that remains to be seen.</p> <p>I think I mostly want to keep improving at the things I’m already doing and who knows, maybe speak for more people than ever before.</p> <p>I’d love to work a bit more on the <a href="https://utilitybend.com/blog/im-officially-a-google-developer-expert">Google Developer Expert</a> program and i’m brainstorming about doing a bit more for students as well. Maybe this is something <a href="https://www.iodigital.com" target="_blank" rel="noreferrer noopener">iO</a> can support me in during the course of next year by allowing me to spend some extra time on it.</p> <p>So, in contrast to last year, I haven’t got anything specific except for getting more involved with the community and improving myself as a speaker and just keep writing those articles.</p> <p>I wish everyone of my readers a happy new year! Enjoy some time with family and friends and even though there are a lot of issues in the world, never forget those close to you, they are so important. Treat each other with respect and patience and make 2024, the best year ever.</p> <p>PS: As by tradition: a CSS holiday card:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Fireworks - Happy New Year 2024" src="https://codepen.io/utilitybend/embed/preview/bGZbLBq?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/bGZbLBq"> Fireworks - Happy New Year 2024</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div>Brecht De RuyteCSS Scroll Snapping Aligned With Global Page Layout: A Full-Width Slider Case Studyhttps://utilitybend.com/blog/css-scroll-snapping-aligned-global-page-layout-case-study/https://utilitybend.com/blog/css-scroll-snapping-aligned-global-page-layout-case-study/Have you run into a situation where you need the padding of one element to align with the padding of another element? It’s a common debacle, especially when a page layout is set with global padding? In this article I demonstrate that issue with a full-width slider component that breaks out of the main page container using a couple of techniques to keep it visually aligned with other elements on the page.Wed, 13 Dec 2023 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2023/12/css-scroll-snapping-aligned-global-page-layout-case-study/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteElevate your CSS debugging skills with these Chrome DevTools tricks in 2024https://utilitybend.com/blog/elevate-your-css-debugging-skills-with-these-chrome-devtools-tricks-in-2024/https://utilitybend.com/blog/elevate-your-css-debugging-skills-with-these-chrome-devtools-tricks-in-2024/From layers and specificity to nesting, from HD color to (scroll) animations. If there is one thing that makes me keep using Chrome as my default browser while developing, it has to be the DevTools and the information we can find about them. As we’re slowly entering the last part of the year, I thought a cool roundup of new CSS debugging tools would be a great article. Let’s dive into it.Tue, 14 Nov 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual-devtools-css-edition.-cr-I_3V_Z1a2fNr.avif 375w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z2hkFas.avif 480w, /_astro/visual-devtools-css-edition.-cr-I_3V_1ApJD3.avif 680w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z14XvJu.avif 800w, /_astro/visual-devtools-css-edition.-cr-I_3V_Zom0h.avif 980w, /_astro/visual-devtools-css-edition.-cr-I_3V_ZRD8GI.avif 1024w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z1lziTs.avif 1660w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z1xeznj.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-devtools-css-edition.-cr-I_3V_1oAz1l.webp 375w, /_astro/visual-devtools-css-edition.-cr-I_3V_hi9Ek.webp 480w, /_astro/visual-devtools-css-edition.-cr-I_3V_ZU8yl6.webp 680w, /_astro/visual-devtools-css-edition.-cr-I_3V_1tEj5i.webp 800w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z2vWEYq.webp 980w, /_astro/visual-devtools-css-edition.-cr-I_3V_2r6vOE.webp 1024w, /_astro/visual-devtools-css-edition.-cr-I_3V_1XalBU.webp 1660w, /_astro/visual-devtools-css-edition.-cr-I_3V_Zd0Rij.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-devtools-css-edition.-cr-I_3V_Z2lpe9K.jpg" srcset="/_astro/visual-devtools-css-edition.-cr-I_3V_Foyf5.jpg 375w, /_astro/visual-devtools-css-edition.-cr-I_3V_ZqSQ6V.jpg 480w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z1Dkz7m.jpg 680w, /_astro/visual-devtools-css-edition.-cr-I_3V_Ksij2.jpg 800w, /_astro/visual-devtools-css-edition.-cr-I_3V_1P2s3f.jpg 980w, /_astro/visual-devtools-css-edition.-cr-I_3V_jMH5g.jpg 1024w, /_astro/visual-devtools-css-edition.-cr-I_3V_Z98s7t.jpg 1660w, /_astro/visual-devtools-css-edition.-cr-I_3V_b0IgD.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="viewing-css-layers">Viewing CSS layers</h2> <p>I’m actually starting off with a feature that was added before 2023, but one that I’ve only been using recently as I was working on a modern CSS “bootstrap file”.</p> <p>When working with multiple layers, especially across multiple files, it is possible to get a bit lost. Thankfully, DevTools has got our back as we can now see in which layer a selector was specified by looking right above it in our style panel:</p> <picture> <source srcset="/_astro/layers-1.BkS3YVde_Z1nXb3E.avif 320w, /_astro/layers-1.BkS3YVde_11Oo2k.avif 480w, /_astro/layers-1.BkS3YVde_Pm5S.avif 800w, /_astro/layers-1.BkS3YVde_ZJzGkM.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/layers-1.BkS3YVde_Z4zbrK.webp 320w, /_astro/layers-1.BkS3YVde_2ldnDe.webp 480w, /_astro/layers-1.BkS3YVde_1kelGM.webp 800w, /_astro/layers-1.BkS3YVde_yNig7.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/layers-1.BkS3YVde_i4Jdy.png" srcset="/_astro/layers-1.BkS3YVde_Z1MVCr1.png 320w, /_astro/layers-1.BkS3YVde_BPVDX.png 480w, /_astro/layers-1.BkS3YVde_Zo85ht.png 800w, /_astro/layers-1.BkS3YVde_Z19y8I9.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="CSS Layers divide the styles in the DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="2184" height="1212"> </picture> <p>It does get a bit better than this though. If we’d like to see a great overview of our layers, we can just click on the layer itself to view where this layer is added in our cascade. This is especially handy when working with a lot of nested layers, to keep our sanity in check:</p> <picture> <source srcset="/_astro/layers-2.DoOvlbCe_2957gP.avif 320w, /_astro/layers-2.DoOvlbCe_Zvjrr7.avif 480w, /_astro/layers-2.DoOvlbCe_Z1witny.avif 800w, /_astro/layers-2.DoOvlbCe_Z2hIwOe.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/layers-2.DoOvlbCe_Z1BI1Vc.webp 320w, /_astro/layers-2.DoOvlbCe_N4x9M.webp 480w, /_astro/layers-2.DoOvlbCe_ZcTtLE.webp 800w, /_astro/layers-2.DoOvlbCe_ZXkxdk.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/layers-2.DoOvlbCe_1PsuoO.png" srcset="/_astro/layers-2.DoOvlbCe_1J6ESt.png 320w, /_astro/layers-2.DoOvlbCe_ZUhSOt.png 480w, /_astro/layers-2.DoOvlbCe_Z1VgUKU.png 800w, /_astro/layers-2.DoOvlbCe_2nu9Bl.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="CSS Layers tree panel in the DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="2378" height="1320"> </picture> <p>If we just want to view the layer tree, we can also click this little Icon in the style panel</p> <picture> <source srcset="/_astro/layers-3.g37lY-eA_LjXNL.avif 320w, /_astro/layers-3.g37lY-eA_1SKFtC.avif 480w, /_astro/layers-3.g37lY-eA_Z12Pmjj.avif 800w, /_astro/layers-3.g37lY-eA_Z1XMgEh.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/layers-3.g37lY-eA_Z21EDqB.webp 320w, /_astro/layers-3.g37lY-eA_1rkGzO.webp 480w, /_astro/layers-3.g37lY-eA_Z1ugld7.webp 800w, /_astro/layers-3.g37lY-eA_Z2qdfy5.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/layers-3.g37lY-eA_2tk5gS.png" srcset="/_astro/layers-3.g37lY-eA_I4uaW.png 320w, /_astro/layers-3.g37lY-eA_eSBKd.png 480w, /_astro/layers-3.g37lY-eA_2ntHLd.png 800w, /_astro/layers-3.g37lY-eA_1rwNqf.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="CSS Layers toggle button" loading="lazy" decoding="async" fetchpriority="auto" width="1177" height="335"> </picture> <p>At first sight, some people might think this really isn’t something with a big use case, but if we work on multiple projects in multiple layers and start nesting these layers as well, it can really help us out:</p> <pre class="language-css" data-language="css"><code is:raw="" 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 property">--font-family-heading</span><span class="token punctuation">:</span> Georgia<span class="token punctuation">,</span> times<span class="token punctuation">,</span> serif<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> components.typography</span> <span class="token punctuation">{</span> <span class="token selector">h1, .h1</span> <span class="token punctuation">{</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--font-family-heading<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>Layers are starting to get used a lot more often, so it’s great to have these tools.</p> <h2 id="checking-selector-specificity">Checking selector specificity</h2> <p>This little update might’ve gone unnoticed for some, but it’s one of those little additions that can really help while debugging or just building our design system.</p> <p>When we hover over a selector, we now get to see the specificity:</p> <picture> <source srcset="/_astro/specificity.Sr6VxTKa_qUpQe.avif 320w, /_astro/specificity.Sr6VxTKa_ZRmJDL.avif 480w, /_astro/specificity.Sr6VxTKa_1c27Qv.avif 800w, /_astro/specificity.Sr6VxTKa_Z1gOE4U.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/specificity.Sr6VxTKa_Bw43H.webp 320w, /_astro/specificity.Sr6VxTKa_ZGL6ri.webp 480w, /_astro/specificity.Sr6VxTKa_1urTnj.webp 800w, /_astro/specificity.Sr6VxTKa_ZXoRy7.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/specificity.Sr6VxTKa_2tlimS.png" srcset="/_astro/specificity.Sr6VxTKa_Z24Scn5.png 320w, /_astro/specificity.Sr6VxTKa_1G0KUQ.png 480w, /_astro/specificity.Sr6VxTKa_ZGc888.png 800w, /_astro/specificity.Sr6VxTKa_1U8dJn.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Specificity tooltip in DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="1814" height="360"> </picture> <p>No more guessing or using external tools for this, we can just view it in our devtools. I love how sometimes little additions can have a big impact, this is one of those.</p> <p><strong>No idea what these three digits are about?</strong><br> You can <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity" target="_blank" rel="noreferrer noopener">read up on it on MDN</a>, but if you’d rather have someone explain it to you, you can always <a href="https://youtu.be/zEPXyqj7pEA?si=3IqYU3DfrvzCulCW&t=811" target="_blank" rel="noreferrer noopener">watch this part of a talk by Bram.us</a>.</p> <h2 id="clear-nesting-support">Clear nesting support</h2> <p>This year, we also got a bit of extra tooling when it comes to nesting. It’s just another one of those quality of life improvements that shows the parent selector grayed out in the styles panel. Once again, a little visual update, that goes a long way!</p> <picture> <source srcset="/_astro/nesting.DTczEAf5_yppLs.avif 320w, /_astro/nesting.DTczEAf5_1PbWie.avif 480w, /_astro/nesting.DTczEAf5_1cwRrw.avif 800w, /_astro/nesting.DTczEAf5_Z7Jg42.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/nesting.DTczEAf5_1LW5aE.webp 320w, /_astro/nesting.DTczEAf5_Z21sw7v.webp 480w, /_astro/nesting.DTczEAf5_Z1ArJMQ.webp 800w, /_astro/nesting.DTczEAf5_29sfuw.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/nesting.DTczEAf5_2hb2Xo.png" srcset="/_astro/nesting.DTczEAf5_Z23Mf95.png 320w, /_astro/nesting.DTczEAf5_ZM0HCj.png 480w, /_astro/nesting.DTczEAf5_19hnNH.png 800w, /_astro/nesting.DTczEAf5_ZaYJGQ.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Grayed out parent selector nesting in DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="1041" height="210"> </picture> <h2 id="hd-color-formats">HD color formats</h2> <p>We have all these new color spaces in CSS and they are so much fun to play around with. We might need to add fallbacks for browsers that don’t support it yet when using them. However - in DevTools - we can easily see when we leave the sRGB color space when opening the color panel in the styles panel (notice the area marked with a red border).</p> <picture> <source srcset="/_astro/color-2.CWj3RzZv_1Y25Uy.avif 320w, /_astro/color-2.CWj3RzZv_2xuup.avif 480w, /_astro/color-2.CWj3RzZv_Z2pzdRq.avif 800w, /_astro/color-2.CWj3RzZv_Z294wOp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/color-2.CWj3RzZv_1wB71K.webp 320w, /_astro/color-2.CWj3RzZv_ZoRtoo.webp 480w, /_astro/color-2.CWj3RzZv_2dbV2H.webp 800w, /_astro/color-2.CWj3RzZv_2tGC5I.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/color-2.CWj3RzZv_Zf9cWk.png" srcset="/_astro/color-2.CWj3RzZv_ka2c9.png 320w, /_astro/color-2.CWj3RzZv_Z1Bjye0.png 480w, /_astro/color-2.CWj3RzZv_10JQd6.png 800w, /_astro/color-2.CWj3RzZv_1hfxg7.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="The CSS Color panel of DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="2360" height="1312"> </picture> <p>In this example, I was using an oklch. There is a simple way to change the current color space we’re working in by clicking the area marked in green on the screenshot above. I’m not sure if this is something we’ll use a lot, unless designing straight in the browser. But it did help me out creating some of those demo’s.</p> <picture> <source srcset="/_astro/color-3.U34BPoXe_ZHcX1u.avif 320w, /_astro/color-3.U34BPoXe_2oeF2f.avif 480w, /_astro/color-3.U34BPoXe_1gjxbg.avif 800w, /_astro/color-3.U34BPoXe_106Ikp.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/color-3.U34BPoXe_Z19CVUi.webp 320w, /_astro/color-3.U34BPoXe_1VNG8r.webp 480w, /_astro/color-3.U34BPoXe_NSyhs.webp 800w, /_astro/color-3.U34BPoXe_xFJqB.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/color-3.U34BPoXe_Z2wmYoU.png" srcset="/_astro/color-3.U34BPoXe_Z2m51JT.png 320w, /_astro/color-3.U34BPoXe_JmBiP.png 480w, /_astro/color-3.U34BPoXe_Znxvx9.png 800w, /_astro/color-3.U34BPoXe_ZDKko0.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Switching color spec in DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="2348" height="1200"> </picture> <p>The demo i was referring to is the following one:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Cocktail glass with oklch slider and color mix" src="https://codepen.io/utilitybend/embed/preview/OJaqMdK?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/OJaqMdK"> Cocktail glass with oklch slider and color mix</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="linear-timing-support-in-the-easing-editor">Linear timing support in the easing editor</h2> <p>When using the <code>animation-timing-function: linear();</code> function, we can now easily update the curves by clicking on it and dragging around in the curve. Just left click to add an extra point and double click to remove it, there are also some presets to be found on the left.</p> <picture> <source srcset="/_astro/linear-timeline-1.BnuR2F9F_2an9ji.avif 320w, /_astro/linear-timeline-1.BnuR2F9F_Z14uBqw.avif 480w, /_astro/linear-timeline-1.BnuR2F9F_Z7Mezx.avif 800w, /_astro/linear-timeline-1.BnuR2F9F_ZLDgaT.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/linear-timeline-1.BnuR2F9F_Z14uIDb.webp 320w, /_astro/linear-timeline-1.BnuR2F9F_KNDpV.webp 480w, /_astro/linear-timeline-1.BnuR2F9F_1Hw1gU.webp 800w, /_astro/linear-timeline-1.BnuR2F9F_13EYFy.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/linear-timeline-1.BnuR2F9F_b4bBr.png" srcset="/_astro/linear-timeline-1.BnuR2F9F_Z2gaEXF.png 320w, /_astro/linear-timeline-1.BnuR2F9F_ZpQhTy.png 480w, /_astro/linear-timeline-1.BnuR2F9F_vQ4Vq.png 800w, /_astro/linear-timeline-1.BnuR2F9F_Z7YVDV.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Linear animation timing curve" loading="lazy" decoding="async" fetchpriority="auto" width="1200" height="1002"> </picture> <p>🔥 <strong>Extra hot tip:</strong> Did you know that by clicking the arrows at the bottom of this little popover you can get a lot more presets created by <code>cubic-bezier()</code>? Pretty cool for tweaking those animations and want to find something “just right”.</p> <picture> <source srcset="/_astro/linear-timeline-2.SO7YMuQB_Z1WNRvN.avif 320w, /_astro/linear-timeline-2.SO7YMuQB_ZeCOrp.avif 480w, /_astro/linear-timeline-2.SO7YMuQB_1UK7nk.avif 800w, /_astro/linear-timeline-2.SO7YMuQB_Z94llU.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/linear-timeline-2.SO7YMuQB_Zg7W2c.webp 320w, /_astro/linear-timeline-2.SO7YMuQB_1s362c.webp 480w, /_astro/linear-timeline-2.SO7YMuQB_Z1rK5W0.webp 800w, /_astro/linear-timeline-2.SO7YMuQB_1xBz7G.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/linear-timeline-2.SO7YMuQB_ZYqnVA.png" srcset="/_astro/linear-timeline-2.SO7YMuQB_2tCHkA.png 320w, /_astro/linear-timeline-2.SO7YMuQB_ZRnnoW.png 480w, /_astro/linear-timeline-2.SO7YMuQB_1i0ypM.png 800w, /_astro/linear-timeline-2.SO7YMuQB_ZLNTjs.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Linear animation timing arrows that shows presets" loading="lazy" decoding="async" fetchpriority="auto" width="1200" height="878"> </picture> <h2 id="transparency-preference-in-the-rendering-tab">Transparency preference in the rendering tab</h2> <p>The rendering tab is probably one of the tabs I use the most for that final accessibility check and test for reduced animations, vision deficiencies, etc. I talked the <a href="https://utilitybend.com/blog/chrome-devtools-tricks-that-deserve-a-spotlight">rendering panel in a previous article</a> and used the contrast emulations for a little <a href="https://www.smashingmagazine.com/2023/01/creating-high-contrast-design-system-css-custom-properties/" target="_blank" rel="noreferrer noopener">article on Smashing Magazine</a>. It has something new though.</p> <p>We can now check if a user has toggled a reduced transparency preference.</p> <picture> <source srcset="/_astro/prefers-reduced-transparency.CrOLhWfq_Z245Aiy.avif 320w, /_astro/prefers-reduced-transparency.CrOLhWfq_Z1n49E3.avif 480w, /_astro/prefers-reduced-transparency.CrOLhWfq_I3HeV.avif 800w, /_astro/prefers-reduced-transparency.CrOLhWfq_ZtHLU1.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/prefers-reduced-transparency.CrOLhWfq_uxewe.webp 320w, /_astro/prefers-reduced-transparency.CrOLhWfq_1byFaJ.webp 480w, /_astro/prefers-reduced-transparency.CrOLhWfq_Z1MuAJd.webp 800w, /_astro/prefers-reduced-transparency.CrOLhWfq_24U2TL.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/prefers-reduced-transparency.CrOLhWfq_Z25m6PY.png" srcset="/_astro/prefers-reduced-transparency.CrOLhWfq_12wbi0.png 320w, /_astro/prefers-reduced-transparency.CrOLhWfq_1IxBVv.png 480w, /_astro/prefers-reduced-transparency.CrOLhWfq_Z1fvDXr.png 800w, /_astro/prefers-reduced-transparency.CrOLhWfq_Z2si98o.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Reduce transparency option in CSS rendering tabs" loading="lazy" decoding="async" fetchpriority="auto" width="2460" height="892"> </picture> <p>This goes hand in hand with a new media feature query (currently only in Chrome and Edge):</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.transparent</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.5<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>prefers-reduced-transparency<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.transparent</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.85<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <h3 id="why-reduced-transparency-matters">Why reduced transparency matters</h3> <p>Various operating systems provide a preference for reducing transparency, and user agents are likely to rely on these system settings. For now, this feature query is only supported in Chrome and Edge, but I’m sure other browsers will follow. Especially as this is a setting that can be set on both iOS and macOS as well.</p> <p>I think the ability to make the web platform an extension of user systems in their operating system is a great thing. Especially for applications running on the web.</p> <h2 id="editing-property-defined-custom-properties">Editing @property defined custom properties</h2> <p>In case you missed it, we are finally getting full browser support for @property very soon with Firefox planning to release it in the next version. In Chrome DevTools we now have the ability to do some debugging, the @property declarations are grouped together and we can change the values in the style panel. This will come in handy and also reminds me that I really want to write about @property sometime.</p> <picture> <source srcset="/_astro/at-property.GaOJtFQW_ZSjxYk.avif 320w, /_astro/at-property.GaOJtFQW_12rE58.avif 480w, /_astro/at-property.GaOJtFQW_ZuGm2M.avif 800w, /_astro/at-property.GaOJtFQW_kcQq0.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/at-property.GaOJtFQW_ZHHTLQ.webp 320w, /_astro/at-property.GaOJtFQW_1d3ihB.webp 480w, /_astro/at-property.GaOJtFQW_ZcgzvY.webp 800w, /_astro/at-property.GaOJtFQW_CCCVN.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/at-property.GaOJtFQW_Z1Szl7P.png" srcset="/_astro/at-property.GaOJtFQW_1F3WAi.png 320w, /_astro/at-property.GaOJtFQW_Z1tlX9b.png 480w, /_astro/at-property.GaOJtFQW_Z2nUC2q.png 800w, /_astro/at-property.GaOJtFQW_Z1y1oyD.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="At property update possibilities" loading="lazy" decoding="async" fetchpriority="auto" width="1180" height="200"> </picture> <h2 id="bonus-scroll-driven-animations-debugging">Bonus: Scroll Driven Animations debugging</h2> <p>I’m a big fan of the new scroll driven animations spec, I even updated my homepage with it. But now, there is a way to easily debug it. This is a bonus as it is an extension that we can add to our devtools. The extension can be found at the chrome web store. <a href="https://chrome.google.com/webstore/detail/scroll-driven-animations/ojihehfngalmpghicjgbfdmloiifhoce" rel="noreferrer noopener" target="_blank">Add the SDA debug tool to Chrome here</a>.</p> <p>When installed we get an extra panel next to the styles, layout, accessibility panels. This will give us a little minimap when using scroll driven animations:</p> <picture> <source srcset="/_astro/sda-1.CnApHkRb_13TqJA.avif 320w, /_astro/sda-1.CnApHkRb_Z7bS3C.avif 480w, /_astro/sda-1.CnApHkRb_fxGSu.avif 800w, /_astro/sda-1.CnApHkRb_ZisSJh.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sda-1.CnApHkRb_2hr68M.webp 320w, /_astro/sda-1.CnApHkRb_16kLkz.webp 480w, /_astro/sda-1.CnApHkRb_1t5mhG.webp 800w, /_astro/sda-1.CnApHkRb_U3KDU.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sda-1.CnApHkRb_Z1YjgOs.png" srcset="/_astro/sda-1.CnApHkRb_Z1yieaW.png 320w, /_astro/sda-1.CnApHkRb_2kMzOL.png 480w, /_astro/sda-1.CnApHkRb_Z2mDX23.png 800w, /_astro/sda-1.CnApHkRb_29vz97.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scroll Driven Animations debug panel" loading="lazy" decoding="async" fetchpriority="auto" width="1186" height="1021"> </picture> <p>This is so cool! Especially because I still haven’t completely wrapped my head around the terminology yet (eg: cover, contain, crossing, etc). This tool gives us the chance to play around with these values and makes life just a bit easier:</p> <picture> <source srcset="/_astro/sda-2.DGugrKCS_Bnhru.avif 320w, /_astro/sda-2.DGugrKCS_HV1zh.avif 480w, /_astro/sda-2.DGugrKCS_1Cmyaw.avif 800w, /_astro/sda-2.DGugrKCS_13tudC.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sda-2.DGugrKCS_jLaj9.webp 320w, /_astro/sda-2.DGugrKCS_qjTqV.webp 480w, /_astro/sda-2.DGugrKCS_1kKr2b.webp 800w, /_astro/sda-2.DGugrKCS_KRn5h.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sda-2.DGugrKCS_2uQns1.png" srcset="/_astro/sda-2.DGugrKCS_Z1AR3iS.png 320w, /_astro/sda-2.DGugrKCS_Z1ujjb6.png 480w, /_astro/sda-2.DGugrKCS_ZzRLzQ.png 800w, /_astro/sda-2.DGugrKCS_Z19KPwK.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scroll Driven Animations debug panel enter and exit options" loading="lazy" decoding="async" fetchpriority="auto" width="1186" height="978"> </picture> <p>For more ins and outs on how this tool works, check out the video on the Chrome web store page. One note though, we can not access this tool via iframes, so if we want to test it with a codepen, we have to use debug mode in our pens. <a href="https://cdpn.io/pen/debug/dyQmQYy" rel="noreferrer noopener" target="_blank">You can use this link if you want to do a quick test.</a></p> <p>Maybe in the future we can have a native version of this in DevTools, removing some of the restrictions of a plugin.</p> <h2 id="conclusion">Conclusion</h2> <p>Most of the things I wrote about here are just about those little ease of life things. Those things that have been updated but might’ve gotten under our radar. These little changes really create a big impact when put together. I’m sure not all of these are little when it comes to developing them, so a big kudos to the DevTools team.</p> <p>I have a feeling many more things are coming our way for animations (maybe a native DevTools Scroll Driven Animations debugger?). This would make sense because the DevTools Team asked a lot about the animations panel earlier on CSS Day this year and we also have a lot of new animation stuff, not only on scroll, but also view transitions.</p> <p>So who knows, maybe some cool big animation update in 2024? Let’s see.</p> <p>Hungry for more DevTools:</p> <ul> <li> <a href="https://developer.chrome.com/tags/devtools/" target="_blank" rel="noreferrer noopener">Google DevTools Blog</a> </li> <li> <a href="https://www.youtube.com/@ChromeDevs" target="_blank" rel="noreferrer noopener">Google Chrome Developers YouTube channel</a> </li> </ul>Brecht De RuyteAnimations and transitions from and to display none with @starting-stylehttps://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style/https://utilitybend.com/blog/animations-and-transitions-from-and-to-display-none-with-at-starting-style/One of the frustrating things when animating elements was not being able to transition to and from display none. Related to that, it also wasn’t an easy feat to use transitions for elements entering the DOM. In Chrome 117 we have the release of @starting-style, which will make animating or transitioning those cases a lot more convenient.Tue, 10 Oct 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual-transition-starting-style.DofMEUe-_ZAQEqB.avif 375w, /_astro/visual-transition-starting-style.DofMEUe-_fmODP.avif 480w, /_astro/visual-transition-starting-style.DofMEUe-_Z20mWii.avif 680w, /_astro/visual-transition-starting-style.DofMEUe-_2diCJt.avif 800w, /_astro/visual-transition-starting-style.DofMEUe-_1mcLg2.avif 980w, /_astro/visual-transition-starting-style.DofMEUe-_Z1biWiR.avif 1024w, /_astro/visual-transition-starting-style.DofMEUe-_IjSAE.avif 1660w, /_astro/visual-transition-starting-style.DofMEUe-_1VfWho.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-transition-starting-style.DofMEUe-_Z2d9bq4.webp 375w, /_astro/visual-transition-starting-style.DofMEUe-_Z1lTGkC.webp 480w, /_astro/visual-transition-starting-style.DofMEUe-_1swEwb.webp 680w, /_astro/visual-transition-starting-style.DofMEUe-_B16K1.webp 800w, /_astro/visual-transition-starting-style.DofMEUe-_Zf4JIq.webp 980w, /_astro/visual-transition-starting-style.DofMEUe-_ZhdKPK.webp 1024w, /_astro/visual-transition-starting-style.DofMEUe-_1Cp53L.webp 1660w, /_astro/visual-transition-starting-style.DofMEUe-_Zzi5kE.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-transition-starting-style.DofMEUe-_ZDQYJc.jpg" srcset="/_astro/visual-transition-starting-style.DofMEUe-_IGf5.jpg 375w, /_astro/visual-transition-starting-style.DofMEUe-_QXbkw.jpg 480w, /_astro/visual-transition-starting-style.DofMEUe-_Z1nLABB.jpg 680w, /_astro/visual-transition-starting-style.DofMEUe-_Z2fi9nL.jpg 800w, /_astro/visual-transition-starting-style.DofMEUe-_1XN7VI.jpg 980w, /_astro/visual-transition-starting-style.DofMEUe-_Z2pCvog.jpg 1024w, /_astro/visual-transition-starting-style.DofMEUe-_ZuYEtJ.jpg 1660w, /_astro/visual-transition-starting-style.DofMEUe-_ZJhlIg.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A todo list that is animated" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="situating-the-problem---transitioning-and-animating-display-none">Situating the problem - transitioning and animating display: none</h2> <p>Animating from and to display none was something we could only achieve in a hacky way. We could play around with JavaScript to change classes or use some other hacks. However, we do have number of reasons why we want animation to work with the display property:</p> <ul> <li><code>display: none;</code> is not the same as a full transparency with opacity, because with display none, it’s hidden for all users and devices (including screen readers)</li> <li>We don’t want the elements to keep the space when fading out, but instead be completely gone.</li> <li>We might want to make it easier for users to understand what is happening on the page. For example, you could animate a loading spinner popping in while a page is loading, or animate a success message in when a user completes a task.</li> </ul> <p><strong>So, why was this so hard? The CSS transitioning spec:</strong></p> <blockquote> <p>In Level 1 of this specification, transitions can only start during a style change event for elements which have a defined before-change style established by the previous style change event. That means a transition could not be started on an element that was not being rendered for the previous style change event</p> </blockquote> <p><em><a href="https://www.w3.org/TR/css-transitions-2/#defining-before-change-style">Quote from CSS Transitions Level 2 spec</a></em></p> <p>In simple terms, this means that you cannot start a transition on an element that is hidden or that has just been created.</p> <h2 id="entering-css-transitions-level-2-and-starting-style">Entering CSS transitions level 2 and @starting-style</h2> <p>This is a quick (simplified) deciphering of the “before-change style” explanation of the CSS Transitions Level 2 spec.</p> <p>The <code>@starting-style</code> rule can be used to define the starting style for an element. The starting style is the style that a transition will start from when the element does not have a previous style.</p> <p>The <code>@starting-style</code> rule is only applied to elements that were not rendered or not a part of the DOM during the previous <a href="https://www.w3.org/TR/css-transitions-1/#style-change-event">style change event</a>. For example, if you add an element to the DOM and then start a transition on that element, the transition will start from the element’s initial style, defined in the <code>@starting-style</code> rules.</p> <p><strong>Note:</strong> I thought for this article, it could be interesting to dig a bit into the spec, so that’s why I wanted to summarize it a little. It’s a real “down the rabbit hole” feeling when digging in these specs. But let’s get started with some of those demo’s now, as a bit of code can say more than a thousand words ;)</p> <h2 id="using-keyframes-from-and-to-display-none">Using keyframes from and to display none</h2> <p>Prior to writing this article, I didn’t realize that animating to and from <code>display: none</code> with CSS keyframes was already supported in Chrome 116. That’s great!</p> <p>A simple zoom out animation that ends with a <code>display: none</code> can be achieve by just doing the following (nothing extra needed):</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.zoom-out</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> zoomOut 0.3s linear forwards<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> zoomOut</span> <span class="token punctuation">{</span> <span class="token selector">from</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 punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">scale3d</span><span class="token punctuation">(</span>0.3<span class="token punctuation">,</span> 0.3<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 selector">to</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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> <p>That way, you get a great animation, while still keeping the benefits of <code>display: none</code>.</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Animate to display: none;" src="https://codepen.io/utilitybend/embed/preview/YzdRvwY?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/YzdRvwY"> Animate to display: none;</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="using-transitions-from-and-to-display-none-with-starting-style">Using transitions from and to display none with @starting-style</h2> <p>Taking things a bit further let’s re-create this example but use transitions to animate our cards and create a slow fade-out.</p> <p>For the HTML, we create a simple grid structure that has three types of cards:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>grid<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>card card-error<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>card card-warning<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>card card-success<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>At the top of the page, let’s add three checkboxes that will control which cards are hidden:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>controls<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>hide-error<span class="token punctuation">&quot;</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">&quot;</span>checkbox<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Hide errors <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>hide-warning<span class="token punctuation">&quot;</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">&quot;</span>checkbox<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Hide warnings <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">&gt;</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">&quot;</span>hide-success<span class="token punctuation">&quot;</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">&quot;</span>checkbox<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Hide success <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now all that remains for us, is to fade out the cards based on a selection using CSS transitions and also make sure they are actually set to <code>display: none;</code> at the end.</p> <p>To do this, two things are necessary, we will need to define the <code>@starting-style</code> but also tell our CSS that it should allow discrete properties to be transitioned. There is a new property for that called <code>transition-behavior</code>.</p> <p><strong>Sidenote: What are discrete properties?</strong></p> <blockquote> <p>Discrete properties in CSS are properties that cannot be interpolated, or smoothly transitioned, between two values. This means that when a discrete property is animated, it will jump abruptly from one value to the other at the end of the animation.</p> </blockquote> <p><em>(or at least, they used to, we can fix this now)</em></p> <p>In the following CSS we set <code>transition-behavior: allow-discrete;</code>, allowing our display property to be transitioned. This can also be added inside of the transition shorthand property, but remember that it <strong>should come in last</strong>. We will set the <code>@starting-style</code> for our grid items to have an <code>opacity</code> of 0 using CSS nesting.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.grid &gt; *</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> opacity 0.7s ease-out<span class="token punctuation">,</span> display 0.7s ease-out<span class="token punctuation">;</span> <span class="token property">transition-behavior</span><span class="token punctuation">:</span> allow-discrete<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">opacity</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>The <code>transition-behavior: allow-discrete</code> property is necessary because the display property is a discrete property. As mentioned before, this means that it cannot be interpolated, or smoothly transitioned, between two values. The <code>transition-behavior: allow-discrete</code> property tells the browser to allow discrete properties to be transitioned.</p> <p>Now that’s a lot of power with just a single property! To actually toggle the states of our checkboxes, we can use the <code>:has()</code> pseudo class, checking the state of the checkboxes (as this was just for demo purposes), for example:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root:has(.hide-error:checked) .card-error</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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>You can play around with it right here:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Warnings and errors show and hide with allow-discrete transitions" src="https://codepen.io/utilitybend/embed/preview/vYvQder?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/vYvQder"> Warnings and errors show and hide with allow-discrete transitions</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="transitioning-an-element-entering-the-dom-with-starting-style">Transitioning an element entering the DOM with @starting-style</h2> <p>A nice benefit of <code>@starting-style</code> is that we can also take control of transitioning elements entering our DOM. Something we do a lot when using JavaScript frameworks.</p> <p>I created a little static todo list where list-items get added with the click of a button. Handling the transforms and opacity purely with CSS transitions. In short, this is how I did it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">li</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">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 0.5s ease-out<span class="token punctuation">,</span> border-radius 0.5s<span class="token punctuation">,</span> opacity 0.3s linear<span class="token punctuation">,</span> background-color 0.3s ease-out<span class="token punctuation">;</span> <span class="token atrule"><span class="token rule">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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>0<span class="token punctuation">,</span> 50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">&amp;.removing</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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>-100%<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token selector">~ li</span> <span class="token punctuation">{</span> <span class="token property">transition</span><span class="token punctuation">:</span> translate 0.5s ease-out<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 -100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>When entering the DOM, the starting style will be taken into account. For this, we do not have to allow discrete properties and just let <code>@starrting-style</code> do its magic. One thing that we can’t do yet is transition to DOM removal, unfortunately. I had to add a bit of custom JS for this adding the “removing” class for half a second before removal:</p> <pre class="language-js" data-language="js"><code is:raw="" class="language-js">removeButton<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token comment">// Remove the list item from the list</span> listItem<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;removing&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> listItem<span class="token punctuation">.</span><span class="token function">remove</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> <span class="token number">500</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> <p>Still, I think it’s pretty neat what we can achieve with just some basic transitioning now, take a look at the demo to find some hidden transitioning gems I added:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Enter and exit dom with @starting-style" src="https://codepen.io/utilitybend/embed/preview/rNoQNNp?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/rNoQNNp"> Enter and exit dom with @starting-style</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="transitioning-from-and-to-the-top-layer">Transitioning from and to the top-layer</h2> <p>There is another place where something is entering and exiting, which is the <code>top-layer</code>. Mostly used for popovers and dialogs for now. For these cases, we can actually handle the entry <strong>and</strong> exit animations using transitions. For a dialog, the open state is defined with the <code>[open]</code> attribute, while popovers have the <code>:popover-open</code> pseudo class.</p> <p>Without the presentation style, for popovers, it can look like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[popover]</span> <span class="token punctuation">{</span> <span class="token property">--transition-time</span><span class="token punctuation">:</span> 0.5s<span class="token punctuation">;</span> <span class="token comment">/* End of animation (on close) styles */</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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>-30px<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> opacity <span class="token function">var</span><span class="token punctuation">(</span>--transition-time<span class="token punctuation">)</span><span class="token punctuation">,</span> transform <span class="token function">var</span><span class="token punctuation">(</span>--transition-time<span class="token punctuation">)</span> ease-out<span class="token punctuation">,</span> display <span class="token function">var</span><span class="token punctuation">(</span>--transition-time<span class="token punctuation">)</span> allow-discrete<span class="token punctuation">;</span> <span class="token selector">&amp;:popover-open</span> <span class="token punctuation">{</span> <span class="token comment">/* Open styles */</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">translateY</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">@starting-style</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Right before open styles */</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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>-30px<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>Here is a quick demo:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Popover transition with @starting-style" src="https://codepen.io/utilitybend/embed/preview/xxmMKbw?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""><p>See the Pen <a href="https://codepen.io/utilitybend/pen/xxmMKbw"> Popover transition with @starting-style</a> by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on <a href="https://codepen.io">CodePen</a>.</p></iframe></div> <h2 id="a-great-thing-to-have">A great thing to have</h2> <p>These starting styles and the ability to animate from and to <code>display: none;</code> are a great thing to have. Personally, I never thought this would be possible some day, so I got pretty adjusted to workarounds by now, so it might take some time adjusting into using this. Unfortunately only on the latest Chrome (117) version for now, but I’m really looking forward to more browser support. Especially in combination with elements in the top-layer it’s a must-have for everything that pops.</p>Brecht De RuyteI'm officially a Google Developer Experthttps://utilitybend.com/blog/im-officially-a-google-developer-expert/https://utilitybend.com/blog/im-officially-a-google-developer-expert/I'm extremely happy to announce that today, I've officially joined the Google Developer Expert program. I'm looking forward to the opportunities it has to offer such as expanding my own horizons as well as providing some support for other developers. An interesting step to take for the future of helping out the developer community.Tue, 03 Oct 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual-gde.CcTkLrCK_1KTmdH.avif 375w, /_astro/visual-gde.CcTkLrCK_Z17QwJP.avif 480w, /_astro/visual-gde.CcTkLrCK_Z202JL3.avif 680w, /_astro/visual-gde.CcTkLrCK_Z1QvoGK.avif 800w, /_astro/visual-gde.CcTkLrCK_ZRB6FM.avif 980w, /_astro/visual-gde.CcTkLrCK_Zf4lp5.avif 1024w, /_astro/visual-gde.CcTkLrCK_Z1gLhE9.avif 1660w, /_astro/visual-gde.CcTkLrCK_RYdcf.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-gde.CcTkLrCK_24k8Jv.webp 375w, /_astro/visual-gde.CcTkLrCK_ZOqKe2.webp 480w, /_astro/visual-gde.CcTkLrCK_Z1GBXff.webp 680w, /_astro/visual-gde.CcTkLrCK_Z1y5CaW.webp 800w, /_astro/visual-gde.CcTkLrCK_Zzbk9Y.webp 980w, /_astro/visual-gde.CcTkLrCK_Z13mtS3.webp 1024w, /_astro/visual-gde.CcTkLrCK_Z254q87.webp 1660w, /_astro/visual-gde.CcTkLrCK_1fKCyV.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-gde.CcTkLrCK_ZLbbth.png" srcset="/_astro/visual-gde.CcTkLrCK_Z7jSKV.png 375w, /_astro/visual-gde.CcTkLrCK_246l4s.png 480w, /_astro/visual-gde.CcTkLrCK_1bU83f.png 680w, /_astro/visual-gde.CcTkLrCK_1krt7x.png 800w, /_astro/visual-gde.CcTkLrCK_2jlL8v.png 980w, /_astro/visual-gde.CcTkLrCK_2d4fAj.png 1024w, /_astro/visual-gde.CcTkLrCK_1bmjlf.png 1660w, /_astro/visual-gde.CcTkLrCK_TmQ7q.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Google Developers Expert logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="opportunities-to-learn-and-share">Opportunities to learn and share</h2> <p>The best thing about becoming a GDE is by far the community. Being able to get a lot of chances to learn and share at the same time. I think it’s a wonderful thing and I’m really looking forward to it.</p> <h2 id="so-im-an-expert-now">So, I’m an “expert” now?</h2> <p>Let’s keep things real. I never was the biggest fan of the word “expert”. I’m very curious by nature and I read and learn a lot of things on a daily basis. I try to learn something new every day. I don’t want to downplay it completely, I’m aware that I know a thing or two, but it’s not like the “expert” badge will change anything from a personal standpoint.</p> <p>I have noticed that I love sharing with the community as I believe that this is an important feature. A lot of developers need to work against the clock, get burned out, or even just find it hard to keep up with all the evolutions on the web. If I can at least help a few people with that, then it’s worth the extra hours I spend on a daily basis, besides my full-time job.</p> <h2 id="web-technologies-because-i-love-the-platform">Web technologies, because I love the platform</h2> <p>I will be taking on the Google Developer Expert role in the category <em>“web technologies, UI and tooling”.</em> This is mostly for my love for good UI and CSS and of course my interest in Open UI.</p> <p>I’m really looking forward to what the Google community will mean for me in the future and I won’t let this chance go to waste, that’s for sure.</p> <p>But first I have some more reading to do and just looking around in this community. Really excited for this. And to all the people who read my little tutorials, stories or tips and tricks. Thank you. It wouldn’t have been possible without you.</p> <p><img src="https://utilitybend.com/_astro/expert-gif.DakDBv4c_2W9Bm.webp" alt="i'm a google developer expert gif" loading="lazy" decoding="async" fetchpriority="auto" width="1280" height="720"></p>Brecht De RuyteGrid ideas: Creating a CSS subgrid utility class for rowshttps://utilitybend.com/blog/grid-ideas-creating-a-css-subgrid-utility-class-for-rows/https://utilitybend.com/blog/grid-ideas-creating-a-css-subgrid-utility-class-for-rows/A full evergreen browser support for CSS subgrid is just around the corner. Time to have a look on how we can create some interesting implementations in our design systems. This article will be focussing on grid-template-rows with subgrid.Tue, 12 Sep 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual.KAToz-f5_Z8njej.avif 375w, /_astro/visual.KAToz-f5_Z1q0gwA.avif 480w, /_astro/visual.KAToz-f5_2eahWB.avif 680w, /_astro/visual.KAToz-f5_Z1665sx.avif 800w, /_astro/visual.KAToz-f5_Z1UdWXh.avif 980w, /_astro/visual.KAToz-f5_Z1IIfIb.avif 1024w, /_astro/visual.KAToz-f5_Z1A9SIb.avif 1660w, /_astro/visual.KAToz-f5_2mL6yd.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.KAToz-f5_28Ockf.webp 375w, /_astro/visual.KAToz-f5_Qcf1X.webp 480w, /_astro/visual.KAToz-f5_ZyOkhL.webp 680w, /_astro/visual.KAToz-f5_1b6q61.webp 800w, /_astro/visual.KAToz-f5_lXxAh.webp 980w, /_astro/visual.KAToz-f5_Z2b9eBY.webp 1024w, /_astro/visual.KAToz-f5_Z22zRBY.webp 1660w, /_astro/visual.KAToz-f5_Z1o22DO.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.KAToz-f5_Z13pFFx.jpg" srcset="/_astro/visual.KAToz-f5_VrDKx.jpg 375w, /_astro/visual.KAToz-f5_ZlaiwJ.jpg 480w, /_astro/visual.KAToz-f5_Z1LbRQt.jpg 680w, /_astro/visual.KAToz-f5_Z1g7sG.jpg 800w, /_astro/visual.KAToz-f5_ZPnYXq.jpg 980w, /_astro/visual.KAToz-f5_1cXQhD.jpg 1024w, /_astro/visual.KAToz-f5_1lxdhD.jpg 1660w, /_astro/visual.KAToz-f5_2a64eN.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A CSS grid with subgrid alignment" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="what-is-this-css-subgrid">What is this CSS subgrid?</h2> <p>Subgrid is part of the level 2 CSS Grid Layout specification and is a value that can be applied to <code>grid-template-columns</code> and <code>grid-template-rows</code>. When you add <code>display: grid;</code> to a container, the direct children become grid items and everything inside of those gets displayed in the normal flow again.</p> <p>By adding this new value to the direct children of our parent grid, we can make the elements inside of our grid items follow the tracks of a parent. A handy feature indeed.</p> <p>That’s enough theory from my side, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Subgrid" target="_blank" rel="noreferrer noopener">A fully, better technical explainer can be found on MDN</a>.</p> <h3 id="what-is-all-the-fuss-about">What is all the fuss about?</h3> <p>To be honest, there are a lot of people that are way more hyped about subgrid than I am, but I did run into a few cases where it can be really handy and it’s especially <em>one</em> of those cases I want to highlight.</p> <p><strong>Let’s take a look at the following grid:</strong></p> <picture> <source srcset="/_astro/bad-grid.Z_pglM6K_vEQjN.avif 320w, /_astro/bad-grid.Z_pglM6K_1Mnjeg.avif 480w, /_astro/bad-grid.Z_pglM6K_FrxHH.avif 800w, /_astro/bad-grid.Z_pglM6K_Z2mVHjC.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/bad-grid.Z_pglM6K_1P3PUH.webp 320w, /_astro/bad-grid.Z_pglM6K_Z1XpOXL.webp 480w, /_astro/bad-grid.Z_pglM6K_1YPxjB.webp 800w, /_astro/bad-grid.Z_pglM6K_Z13xHHI.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/bad-grid.Z_pglM6K_1htFbo.png" srcset="/_astro/bad-grid.Z_pglM6K_6GoVr.png 320w, /_astro/bad-grid.Z_pglM6K_1noQPT.png 480w, /_astro/bad-grid.Z_pglM6K_gt6kl.png 800w, /_astro/bad-grid.Z_pglM6K_2igY6W.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Grid with inconsistent children alignment" loading="lazy" decoding="async" fetchpriority="auto" width="1600" height="800"> </picture> <p>This could be a bit better, right? When we have cards like this, a preferred presentation could be to have the heading, images, paragraphs and buttons aligned on the same track. All in good time, let’s get started with our basic grid utility.</p> <h2 id="the-css-grid-utility-class">The CSS grid utility class</h2> <p>This is one of my favorite ways to add a grid utility class. This idea was shown to me at CSS Day, by an <a href="https://www.youtube.com/live/Y50iqMlrqU8?si=GYndFS8XVyoRAcxJ&t=1067" target="_blank" rel="noreferrer noopener">amazing talk by Stephanie Eckles</a>. I suggest you check that out for a full picture. In order to explain how I got to the idea of my subgrid utility, it’s important to understand the basics of this method first.</p> <p>It all start with a utility class in your HTML</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">&gt;</span></span>grid item<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- bunch of other grid items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now, the following bit of CSS code will create a grid layout where the number of columns adjusts automatically based on the container’s width. Each column has a minimum width of 400 pixels (or 100% if the container is narrower) and shares the remaining space equally. This is a responsive grid layout that ensures the columns are not too narrow on smaller screens while utilizing available space efficiently on larger screens. This way, you get a fluid grid, without having to write a container or media query.</p> <pre class="language-css" data-language="css"><code is:raw="" 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-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>400px<span class="token punctuation">,</span> 100%<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> 24px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Pretty cool right?</p> <p>But it’s not really a utility yet, to achieve this, we need to add some custom properties into the mix. In the example by Stephanie, she created two layers for this, a <em>theme</em> layer for <code>:root</code> custom properties and a <em>layout</em> layer which will include the <code>.grid</code> itself to inherit some of those <code>:root</code> properties.</p> <pre class="language-css" data-language="css"><code is:raw="" 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 property">--layout-column-min</span><span class="token punctuation">:</span> 400px<span class="token punctuation">;</span> <span class="token property">--layout-gap</span><span class="token punctuation">:</span> 24px<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> layout</span> <span class="token punctuation">{</span> <span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">--grid-min</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-column-min<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--grid-gap</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--layout-gap<span class="token punctuation">)</span><span class="token punctuation">;</span> <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><span class="token function">var</span><span class="token punctuation">(</span>--grid-min<span class="token punctuation">)</span><span class="token punctuation">,</span> 100%<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>--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>The benefit of this is that we now have a flexible utility class. If we - for example - would want to make another product grid, which derives from the default. We could just add the following to our HTML and CSS</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid grid-products<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- bunch of items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.grid-products</span> <span class="token punctuation">{</span> <span class="token property">--grid-min</span><span class="token punctuation">:</span> 500px<span class="token punctuation">;</span> <span class="token property">--grid-gap</span><span class="token punctuation">:</span> 16px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This is only the beginning. In the presentation by Stepanie, there is a great idea on how to add a flex utility class based on this as well. But as we’re talking about grid, I’m going to end it here. Here is a codepen of her full approach to grid and flex:</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="grid / flex playground with container queries" src="https://codepen.io/utilitybend/embed/preview/MWZbVyK?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""></iframe></div> <p>One thing I do need to mention is that in this system, all the grid (or flex) items are set to be a container, by adding the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:is(.grid, .flex) &gt; *</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> </code></pre> <p>This allows for some <em>really modern web development</em>! But there is a gotcha about this later on.</p> <h2 id="creating-our-subgrid-utility-class">Creating our subgrid utility class</h2> <p>Our grid is set, but now we want to have a subgrid inside of this. We will need to adjust our markup a bit, because our grid-items on which we want our subgrid on need to be direct children of the parent grid.</p> <h3 id="subgrid-is-powerful-but-not-flexible">Subgrid is powerful, but not flexible</h3> <p>This is one thing about subgrid that’s important to know. It is pretty powerful, but it’s not very flexible. Our grid-items on which we want to put the subgrid value on need to be direct children of a grid. More importantly, we need to know the number of children of our subgrid as well. But let’s not give up on creating a utility class for this.</p> <p>Let’s add a new class to our grid to tell we want the rows to be in a subgrid:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid subgrid-rows<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>A title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>footer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>Some button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>footer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- more of those article elemnts --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>As you can see, our article on which we want to add the subgrid has 4 direct children (h2, img, p, footer). Based on this, let’s create the default of our utility. At the bottom of our layout layer (or in a separate one behind it) let’s add the following to our CSS:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.subgrid-rows</span> <span class="token punctuation">{</span> <span class="token selector">&gt; *</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> <span class="token function">var</span><span class="token punctuation">(</span>--subgrid-gap<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-row</span><span class="token punctuation">:</span> auto / span <span class="token function">var</span><span class="token punctuation">(</span>--subgrid-rows<span class="token punctuation">,</span> 4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> subgrid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This time, I already provided some custom properties which could be overwritten based on the use case, especially for the amount of tracks (children) and the gap.</p> <p>And here is an example of this (spoiler, it doesn’t work)</p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="subgrid gone wrong" src="https://codepen.io/utilitybend/embed/preview/vYvxRrR?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""></iframe></div> <h3 id="you-cant-combine-container-type-with-a-subgrid">You can’t combine container-type with a subgrid</h3> <p>The <code>container-type</code> property is used to control how a grid item is laid out in its parent grid. The <code>subgrid</code> value of the <code>grid-template-rows</code> property tells the nested grid to use the tracks defined on the parent grid. However, the <code>container-type</code> property can only be applied to grid containers, not grid items. Therefore, we cannot combine the <code>container-type</code> property with the <code>grid-template-rows: subgrid</code> value.</p> <p>So, we could update our default selector that declared that direct children of a grid should be a container. We could overrule it in our utility, or use <code>:not()</code>. I chose the second option.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">:is(.grid:not(.subgrid-rows), .flex) &gt; *</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> </code></pre> <h2 id="making-our-css-subgrid-utility-flexible">Making our CSS Subgrid utility flexible</h2> <p>The question that remains is how can we make this utility as flexible as possible and there are two methods that came to mind when I was creating this:</p> <ol> <li>Adding an inline <code>style</code> attribute to our grid to set <code>--subgrid-rows</code></li> <li>Using <code>:has()</code> and create a bunch of defaults</li> </ol> <p>Method 1 is pretty straight forward, we already added the custom property for this. We could update our HTML with a custom property:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid subgrid-rows<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 4<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- items --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Method 2 does create some unneeded CSS, but on the other hand it can automate some things in the future. You could add the following lines of code into your CSS file</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.subgrid-rows</span> <span class="token punctuation">{</span> <span class="token selector">&amp;:has(&gt; :nth-child(1):last-child)</span> <span class="token punctuation">{</span><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token selector">&amp;:has(&gt; :nth-child(2):last-child)</span> <span class="token punctuation">{</span><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token selector">&amp;:has(&gt; :nth-child(3):last-child)</span> <span class="token punctuation">{</span><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 3<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token selector">&amp;:has(&gt; :nth-child(4):last-child)</span> <span class="token punctuation">{</span><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 4<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token selector">&amp;:has(&gt; :nth-child(5):last-child)</span> <span class="token punctuation">{</span><span class="token property">--subgrid-rows</span><span class="token punctuation">:</span> 5<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token comment">/* and you can keep going... */</span> <span class="token selector">&gt; *</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> <span class="token function">var</span><span class="token punctuation">(</span>--subgrid-gap<span class="token punctuation">,</span> 0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-row</span><span class="token punctuation">:</span> auto / span <span class="token function">var</span><span class="token punctuation">(</span>--subgrid-rows<span class="token punctuation">,</span> 5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> subgrid<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This will count the exact number of children up to 5 and pass this as a custom property to the subgrid.</p> <p>I think it might be dependent on the project or use case to pick a favorite. But I think these are a few valid first drafts.</p> <p>Here are the two examples in action:</p> <p><strong>Method 1 with inline style</strong></p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Subgrid utility" src="https://codepen.io/utilitybend/embed/preview/bGOqame?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""></iframe></div> <p><strong>Method 2 with</strong> <code>:has()</code></p> <div class="embed"><iframe height="300" style="width: 100%;" scrolling="no" title="Subgrid rows with has" src="https://codepen.io/utilitybend/embed/preview/rNoyJWq?default-tab=result&theme-id=dark" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen=""></iframe></div> <h2 id="conclusion-of-css-subgrid">Conclusion of CSS Subgrid</h2> <p>I think subgrid is a fantastic addition to CSS. It however, lacks a bit of flexibility and it might get overwhelming if we have projects full of subgrid tinkering. I do mostly see a bigger benefit in using it for rows than columns, but that’s purely subjective. Surely there are many use cases out there on which it could be handy for columns. I’ll write about it when i need it.</p> <p>It has been a feature that I didn’t use as progressive enhancement because of the big market share of Chrome and Edge. But since it’s just around the corner for both of them, It’s time to do some implementations and further experiment with it.</p> <p><em>Keep tinkering on those grids, y’all</em></p>Brecht De RuyteRecharging batteries - Taking breaks is importanthttps://utilitybend.com/blog/recharging-batteries-taking-breaks-is-important/https://utilitybend.com/blog/recharging-batteries-taking-breaks-is-important/It's been an eventful first half of the year with giving my first presentations, writing a lot of articles, learning a bucketload of new CSS and following along with Open UI. It's been great fun and I do these things with a passion, but even so, it's time for a good break.Tue, 08 Aug 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual.GW2kD87h_gEQ5L.avif 375w, /_astro/visual.GW2kD87h_Z10W6cv.avif 480w, /_astro/visual.GW2kD87h_Z2qXFwf.avif 680w, /_astro/visual.GW2kD87h_ZG2U8s.avif 800w, /_astro/visual.GW2kD87h_Z1vaMDc.avif 980w, /_astro/visual.GW2kD87h_OtGPu.avif 1024w, /_astro/visual.GW2kD87h_X33Pu.avif 1660w, /_astro/visual.GW2kD87h_Z9d4G3.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.GW2kD87h_Z2wjL9B.webp 375w, /_astro/visual.GW2kD87h_1gfpm3.webp 480w, /_astro/visual.GW2kD87h_Z9L9WG.webp 680w, /_astro/visual.GW2kD87h_1A9Aq6.webp 800w, /_astro/visual.GW2kD87h_L1HUm.webp 980w, /_astro/visual.GW2kD87h_n3HVG.webp 1024w, /_astro/visual.GW2kD87h_vC4VG.webp 1660w, /_astro/visual.GW2kD87h_1aaTTQ.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.GW2kD87h_1uMgS8.jpg" srcset="/_astro/visual.GW2kD87h_1luO5C.jpg 375w, /_astro/visual.GW2kD87h_3RQMl.jpg 480w, /_astro/visual.GW2kD87h_Z1m8Hwo.jpg 680w, /_astro/visual.GW2kD87h_nM2Qo.jpg 800w, /_astro/visual.GW2kD87h_ZqkODl.jpg 980w, /_astro/visual.GW2kD87h_Z1j0jWC.jpg 1024w, /_astro/visual.GW2kD87h_Z1aqWWC.jpg 1660w, /_astro/visual.GW2kD87h_ZlS70s.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Drawn hills and sun in CSS" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="being-honest-to-yourself-and-your-company">Being honest to yourself and your company</h2> <p>I always think it’s important to reflect on the time you invest in certain side projects. I love doing things such as following along with Open UI meetings, spreading the word by giving presentations, tutor some CSS and writing about my own take on it. It helps me grow as a developer, as a writer, maybe even as a presenter.</p> <p>All of these things happen in my free time. I do enjoy my job at iO in general as I get to write CSS for a living, but making 3 presentations in 8 months is hard as well as trying to write good content and creating demos. These things are especially hard if you have to do them in evenings and weekends while working on a hard deadlined project. I never get to use any company time for these things, so yes, It does take up a lot of energy.</p> <h2 id="family-matters">Family matters</h2> <p>I am a truly blessed person with a supportive - and just generally fantastic - wife and a healthy daughter who allow me to follow my passion. But not everything is perfect as my dad is still fighting cancer and this is still a struggle. I don’t talk about it that much, for the same reason that I don’t share a lot of pictures of my daughter on social media. I like to keep things a bit private.</p> <p>But I’m looking forward to spending 4 weeks with my daughter, taking walks, maybe teaching her a thing or two, creating awesome foods and sporty activities. It’s going to be awesome! As well as some well-deserved rest together with my wife. Barbecues, board games, all that stuff.</p> <h2 id="hobbies">Hobbies</h2> <p>I don’t get around doing much photography anymore, I hope to pick this up again as well. I do have a few personal “lazy side-projects” I’ve been willing to do: Build that Lego set I’ve never finished, start playing the latest Zelda game and start jamming on that guitar again - cranking it to 11 - just for the heck of it.</p> <h2 id="an-almost-digital-detox">An “almost” digital detox</h2> <p>I’m mainly going to stay away from overly using social media, the day job and writing in August. I will be following the Open UI telecons but that will be pretty much it. Keeping the code to the minimum and enjoying some sunlight and activities. I don’t really have to “turn off social media” as I’m pretty lousy at following up socials anyways.</p> <h2 id="but-mainly-thanks-from-me">But mainly, thanks from me</h2> <p>I’ve noticed a growth in popularity of this blog, people sharing, commenting on Mastodon or Twitter (X?). It’s grown from a handful to thousands and I really want to thank all of you readers, even though I don’t really know who you are. The support really matters, a simple “retweet” or “like” can make my day. This blog has already given me the opportunity to meet a lot of interesting people and I hope to continue this in the future - after - the break.</p> <h2 id="one-last-demo">One last demo</h2> <p>Before I end, some final demos in which I played around with OKLCH colors in Chrome with the theme “summer vibes”. One of them as an “iO challenge” and one of them in my spare time. This time without explainers, if you’d like me to give some more info on them, let me know, but it will have to wait for now…</p> <p>☀️ <strong>Summer mode engage ☀️</strong></p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/OJaqMdK?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/QWJMMxm?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div>Brecht De RuyteScroll driven animations in CSS are a joy to play around with!https://utilitybend.com/blog/scroll-driven-animations-in-css-are-a-joy-to-play-around-with/https://utilitybend.com/blog/scroll-driven-animations-in-css-are-a-joy-to-play-around-with/Scroll Driven Animations are set to be released in Chrome 115, giving us the chance to animate elements based on a scroll instead of time, increasing our toolset to create some fun interactions. I’m sure many great tricks and articles will be found, as this feature opens a lot of possibilities.Mon, 24 Jul 2023 00:00:00 GMT<picture> <source srcset="/_astro/scroll-driven-animations.DEIXDwrC_1zFjHz.avif 375w, /_astro/scroll-driven-animations.DEIXDwrC_2tOOv8.avif 480w, /_astro/scroll-driven-animations.DEIXDwrC_Z1PJuEC.avif 680w, /_astro/scroll-driven-animations.DEIXDwrC_Z1V55Lj.avif 800w, /_astro/scroll-driven-animations.DEIXDwrC_ZuUqDY.avif 980w, /_astro/scroll-driven-animations.DEIXDwrC_X9nab.avif 1024w, /_astro/scroll-driven-animations.DEIXDwrC_Z1U8wfQ.avif 1660w, /_astro/scroll-driven-animations.DEIXDwrC_Z1AAj6P.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/scroll-driven-animations.DEIXDwrC_29U2bE.webp 375w, /_astro/scroll-driven-animations.DEIXDwrC_Z217AOI.webp 480w, /_astro/scroll-driven-animations.DEIXDwrC_Z1guMbx.webp 680w, /_astro/scroll-driven-animations.DEIXDwrC_Z1lPnie.webp 800w, /_astro/scroll-driven-animations.DEIXDwrC_4jgO6.webp 980w, /_astro/scroll-driven-animations.DEIXDwrC_Z1sa6k1.webp 1024w, /_astro/scroll-driven-animations.DEIXDwrC_IJ83S.webp 1660w, /_astro/scroll-driven-animations.DEIXDwrC_ZGv8aU.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/scroll-driven-animations.DEIXDwrC_8e1D1.jpg" srcset="/_astro/scroll-driven-animations.DEIXDwrC_21ULhU.jpg 375w, /_astro/scroll-driven-animations.DEIXDwrC_Z296QIs.jpg 480w, /_astro/scroll-driven-animations.DEIXDwrC_Z1ou35h.jpg 680w, /_astro/scroll-driven-animations.DEIXDwrC_Z1tODbX.jpg 800w, /_astro/scroll-driven-animations.DEIXDwrC_Z3EY4D.jpg 980w, /_astro/scroll-driven-animations.DEIXDwrC_ZqA2jW.jpg 1024w, /_astro/scroll-driven-animations.DEIXDwrC_1Kjc3W.jpg 1660w, /_astro/scroll-driven-animations.DEIXDwrC_A71Vv.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Scroll driven animations example of a timeline" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture> <h2 id="scroll-driven-vs-scroll-triggered">Scroll driven VS scroll triggered</h2> <p>You heard that right, we’re talking about scroll <strong>driven</strong> animations, which is not completely the same as scroll trigger animations. In this case the animation gets played while the user is scrolling the page, using the scrollbar as its timeline. Although there are some tricks on how you can <a href="https://www.bram.us/2023/06/15/scroll-triggered-animations/" target="_blank" rel="noreferrer noopener">achieve a scroll triggered animation by using some modern CSS</a>, you might still want to use an intersection observer in JS for this. These animations can be blazingly fast when using transforms as they run on the compositor in contrast to popular JS methods which run on the main thread increasing the chance of jank.</p> <h2 id="the-basics-to-create-an-animation-on-scroll">The basics to create an animation on scroll</h2> <p>I’ve already written an <a href="https://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css">article which covered the basics on scroll driven animations</a>, but let’s repeat the basics really quick. It starts off with a basic animation in CSS but instead of giving it a timing, we add an animation-timeline:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> reveal linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>inline<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> reveal</span> <span class="token punctuation">{</span> <span class="token selector">0%, 100%</span> <span class="token punctuation">{</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 -25%<span class="token punctuation">;</span> <span class="token property">scale</span><span class="token punctuation">:</span> 0.7<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0<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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p><strong>A few pointers to add about this:</strong></p> <ul> <li>Do not add a timing to your animation</li> <li>The animation-timeline is not part of the animation shorthand</li> </ul> <p>I’m really happy that the CSSWG decided not to add this in the animation shorthand, because - let’s be honest - this shorthand is a bit of a mess and I always seem to forget the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/animation#formal_syntax" target="_blank" rel="noreferrer noopener">correct order for “animation”</a> (luckily, browsers do forgive us most of the time when using this)</p> <h2 id="combining-scroll-driven-animations-with-scroll-snapping">Combining scroll driven animations with scroll snapping</h2> <p>One of the things I love about all these new nifty CSS features is how well they go hand in hand. It seems only yesterday since we had scroll snapping in CSS and now we can already think about combining it with scroll driven animations… It’s pretty wild.</p> <p>As an easy example, I created a Legend of Zelda game timeline with a horizontal scroll.</p> <picture> <source srcset="/_astro/zelda-scroller.JD-Pbejt_2civxf.avif 320w, /_astro/zelda-scroller.JD-Pbejt_1OQN7l.avif 480w, /_astro/zelda-scroller.JD-Pbejt_2vOPK0.avif 800w, /_astro/zelda-scroller.JD-Pbejt_2sN7mj.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/zelda-scroller.JD-Pbejt_Z1tc1nd.webp 320w, /_astro/zelda-scroller.JD-Pbejt_Z1PCIN7.webp 480w, /_astro/zelda-scroller.JD-Pbejt_Z19EGas.webp 800w, /_astro/zelda-scroller.JD-Pbejt_Z1cGpy9.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/zelda-scroller.JD-Pbejt_Z1QOkMz.png" srcset="/_astro/zelda-scroller.JD-Pbejt_Z2aRPI6.png 320w, /_astro/zelda-scroller.JD-Pbejt_2wRzEV.png 480w, /_astro/zelda-scroller.JD-Pbejt_Z1Qlvvl.png 800w, /_astro/zelda-scroller.JD-Pbejt_Z1TneT2.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Zelda timeline scroller" loading="lazy" decoding="async" fetchpriority="auto" width="1600" height="900"> </picture> <p>It basically is a timeline with articles inside of it that has some scroll snapping in the center, the HTML build-up is quite simple:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>timeline<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>article</span><span class="token punctuation">&gt;</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">&quot;</span>...<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>The legend of Zelda<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>time</span><span class="token punctuation">&gt;</span></span>1986<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>time</span><span class="token punctuation">&gt;</span></span> - <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>strong</span><span class="token punctuation">&gt;</span></span>NES<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>strong</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- and a lot more articles for each game --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Next up we have some basic styling by adding the articles next to each other with flexbox and add some general look and feel which I will leave out of the code example in order to stick to the essentials:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.timeline</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">display</span><span class="token punctuation">:</span> flex<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>--gap<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<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">padding-inline</span><span class="token punctuation">:</span> <span class="token function">calc</span><span class="token punctuation">(</span><span class="token punctuation">(</span>100vw - <span class="token function">var</span><span class="token punctuation">(</span>--item-size<span class="token punctuation">)</span><span class="token punctuation">)</span> / 2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.timeline article</span> <span class="token punctuation">{</span> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">scroll-snap-stop</span><span class="token punctuation">:</span> always<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> reveal linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span>inline<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> reveal</span> <span class="token punctuation">{</span> <span class="token selector">0%, 100%</span> <span class="token punctuation">{</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0 -25%<span class="token punctuation">;</span> <span class="token property">scale</span><span class="token punctuation">:</span> 0.7<span class="token punctuation">;</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0.2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</span> <span class="token punctuation">{</span> <span class="token property">scale</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">translate</span><span class="token punctuation">:</span> 0<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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>So, what happened here? We created a centered horizontal scroll snap and added a “reveal” animation to the parent scroll in the inline axis. The animation itself will place the article to its “active” state at 50%, which will be the 50% of its scroll distance and also the place where it snaps.</p> <p>To avoid throwing insane chunks of code in this article, there is a lot more CSS going on for the styling, but when it comes to the scroll animations: The exact same animation technique was used for the images and the info panel that pops out to create the following little demo:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/mdQpxBX?default-tab=result" frameborder="0" allowtransparency="true" style="" loading="lazy"></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Scrolling animation of zelda games horizontal"><source src="/_astro/_zelda-scroller.COwRq485.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="view-timeline-ranges">View Timeline Ranges</h2> <p>Another great feature that comes together with scroll driven animations is the ability to specify ranges of where the animation should play.</p> <p>Current options for these are:</p> <ul> <li><code>cover</code>: Represents the full range of the view progress timeline.</li> <li>entry: Represents the range during which the principal box is entering the view progress visibility range.</li> <li><code>exit</code>: Represents the range during which the principal box is exiting the view progress visibility range.</li> <li><code>entry-crossing</code>: Represents the range during which the principal box crosses the end border edge.</li> <li><code>exit-crossing</code>: Represents the range during which the principal box crosses the start border edge.</li> <li><code>contain</code>: Represents the range during which the principal box is either fully contained by, or fully covers, its view progress visibility range within the scrollport. This depends on whether the subject is taller or shorter than the scroller.</li> </ul> <p>(list from <a href="https://developer.chrome.com/articles/scroll-driven-animations/)" target="_blank" rel="noreferrer noopener">developer.chrome.com/</a></p> <p>You can change the range of where the animation should play by defining a range-start and range-end by giving each of them a name and an offset (percentage, of fixed).</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">div</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> reveal linear both<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">view</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">animation-range</span><span class="token punctuation">:</span> contain 0% entry 80%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>To be completely honest, I find the naming of these ranges quite hard to memorize and I’m really looking forward to DevTools updates to work with them.</p> <p>For now, there is a nice little helper created by <a href="https://www.bram.us/" target="_blank" rel="noreferrer noopener">bram.us</a> with the fantastic title: <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges" target="_blank" rel="noreferrer noopener">The Scroll-driven Animations View Progress Timeline Ranges and Animation Progress Visualizer</a> (say it 10 times, I dare you)</p> <p>Jokes about the title aside, this tool really helped a lot in creating this little timeline demo:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/dyQmQYy?default-tab=result" frameborder="0" allowtransparency="true" style="" loading="lazy"></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Video of timeline scroller with CSS"><source src="/_astro/_timeline-scroller.DXafnrws.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p><strong>Few things to note about this demo:</strong></p> <ul> <li>I used @property to animate the clipping path for my timeline branches and to animate the gradient in the background on scroll</li> <li>Using different animation-ranges to play around with the timing of the scroll animation</li> </ul> <h2 id="multiple-view-timeline-ranges-in-keyframes">Multiple View Timeline Ranges in keyframes</h2> <p>Instead of using the animation-range property, we can also declare multiple ranges inside of our keyframes. Syntax looks like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@keyframes</span> inAndOut</span> <span class="token punctuation">{</span> <span class="token selector">entry 0%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</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> <span class="token selector">entry 100%</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">translateX</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 selector">exit 0%</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">translateX</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 selector">exit 100%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</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> <span class="token punctuation">}</span> </code></pre> <p>For me this feels a bit more natural because it’s actually inside of our keyframes. I would prefer to use this method more than a separate property, but that might just be me.</p> <p>I went a bit over the top with the effect, but created a little demo with this technique as well:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/RwqBymL?default-tab=result" frameborder="0" allowtransparency="true" style="" loading="lazy"></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Scrolling sections in 50% viewport height with scroll driven animations"><source src="/_astro/_architecture-scroller.BIMErqte.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="learning-scroll-driven-animations">Learning scroll driven animations</h2> <p>I know that this article wasn’t much of a tutorial, but more some sort of teaser to get you hyped into this new feature. Creating these demo’s was fun but also didn’t take too long to be honest and that’s a good thing.</p> <p>Although the view timeline ranges are a bit hard to memorize, the basics of working with these scroll driven animations are quite easy to learn (but as many things with CSS, probably hard to master)</p> <h3 id="so-where-should-you-start-when-learning-about-this-spec">So where should you start when learning about this spec:</h3> <p><strong>By bram.us (who contributed a lot to this spec):</strong></p> <ul> <li> <a href="https://scroll-driven-animations.style/" target="_blank" rel="noreferrer noopener">scroll-driven-animations.style</a> </li> <li> <a href="https://developer.chrome.com/articles/scroll-driven-animations/" target="_blank" rel="noreferrer noopener">The original article on the Chrome blog</a> </li> <li> <a href="https://www.youtube.com/watch?v=nFbuXdEU-oA" target="_blank" rel="noreferrer noopener">Talk on CSS-Day</a> </li> <li> <a href="https://www.bram.us/" target="_blank" rel="noreferrer noopener">His blog for further tricks and tips</a> </li> </ul> <p>Other reads:</p> <ul> <li> <a href="https://developer.mozilla.org/en-US/blog/scroll-progress-animations-in-css/" target="_blank" rel="noreferrer noopener">Scroll progress animations in CSS - Michelle Barker</a> </li> <li><a href="https://kizu.dev/fit-to-width-text/" target="_blank" rel="noreferrer noopener">Fit-to-Width text - Roman Komarov</a> ( one to follow for experimental / sometimes hacky usages!)</li> </ul> <p>I’m really looking forward to all the creative demo’s using these techniques in the future. The best part of this feature is that it can be easily implemented as a progressive enhancement. Just adding that little extra touch of animation for an upcoming project. Looking forward to it.</p>Brecht De RuyteGoing beyond constants with custom propertieshttps://utilitybend.com/blog/going-beyond-constants-with-custom-properties/https://utilitybend.com/blog/going-beyond-constants-with-custom-properties/If you love CSS, you've probably heard of custom properties (a.k.a. CSS variables) before. Still, a lot of people seem to use them as constants for their CSS. In this article, I will try to give you some more insights on how you can use these custom properties to create some smart systems or even use them as booleans in CSS and create easy progressive enhancements.Mon, 26 Jun 2023 00:00:00 GMT<picture> <source srcset="/_astro/io.Dg3cHcwk_Z25b8NH.avif 375w, /_astro/io.Dg3cHcwk_tOkKt.avif 480w, /_astro/io.Dg3cHcwk_Z5wM08.avif 680w, /_astro/io.Dg3cHcwk_todc8.avif 800w, /_astro/io.Dg3cHcwk_ZAfldy.avif 980w, /_astro/io.Dg3cHcwk_ZmaGMW.avif 1024w, /_astro/io.Dg3cHcwk_20jHNz.avif 1660w, /_astro/io.Dg3cHcwk_1Wjfzb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/io.Dg3cHcwk_Z1Ab5it.webp 375w, /_astro/io.Dg3cHcwk_XOogH.webp 480w, /_astro/io.Dg3cHcwk_osgv6.webp 680w, /_astro/io.Dg3cHcwk_XogHm.webp 800w, /_astro/io.Dg3cHcwk_Z6fhHk.webp 980w, /_astro/io.Dg3cHcwk_ZmJlnD.webp 1024w, /_astro/io.Dg3cHcwk_1YK4dS.webp 1660w, /_astro/io.Dg3cHcwk_1EH8qP.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/io.Dg3cHcwk_VsBpc.jpg" srcset="/_astro/io.Dg3cHcwk_16WAjf.jpg 375w, /_astro/io.Dg3cHcwk_Z1oe3Uv.jpg 480w, /_astro/io.Dg3cHcwk_Z1XAbG7.jpg 680w, /_astro/io.Dg3cHcwk_Z1oEbtQ.jpg 800w, /_astro/io.Dg3cHcwk_Z2tiJTx.jpg 980w, /_astro/io.Dg3cHcwk_2bttFL.jpg 1024w, /_astro/io.Dg3cHcwk_ZwdevD.jpg 1660w, /_astro/io.Dg3cHcwk_Zirdpp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="iO tech_hub" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://techhub.iodigital.com/articles/going-beyond-constants-with-custom-properties" target="_blank">Read the article at iO tech_hub</a></div>Brecht De RuyteThis was CSS day 2023https://utilitybend.com/blog/this-was-css-day-2023/https://utilitybend.com/blog/this-was-css-day-2023/Another year, another CSS day. I'm not joking or exaggerating that this conference has and always will be one of my highlights of the year. Welcoming, top speakers accompanied with just totally geeking out with other CSS people around food and drinks. This was CSS day 2023 - a short summary.Sat, 10 Jun 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual.DoS9licT_Z24epCp.avif 375w, /_astro/visual.DoS9licT_xzH70.avif 480w, /_astro/visual.DoS9licT_1Mswyg.avif 680w, /_astro/visual.DoS9licT_LLqde.avif 800w, /_astro/visual.DoS9licT_1EuwmU.avif 980w, /_astro/visual.DoS9licT_Z1xdclv.avif 1024w, /_astro/visual.DoS9licT_Z1cyvQN.avif 1660w, /_astro/visual.DoS9licT_1F9FLo.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.DoS9licT_cX5V9.webp 375w, /_astro/visual.DoS9licT_Z2foU8n.webp 480w, /_astro/visual.DoS9licT_Z10w5G7.webp 680w, /_astro/visual.DoS9licT_Z21dc29.webp 800w, /_astro/visual.DoS9licT_Z18u5Rs.webp 980w, /_astro/visual.DoS9licT_Z1YDbfj.webp 1024w, /_astro/visual.DoS9licT_6OsJ6.webp 1660w, /_astro/visual.DoS9licT_Z25DsqD.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.DoS9licT_1WAKS9.jpg" srcset="/_astro/visual.DoS9licT_ZYorCy.jpg 375w, /_astro/visual.DoS9licT_1CpF6Q.jpg 480w, /_astro/visual.DoS9licT_Z2cSDfO.jpg 680w, /_astro/visual.DoS9licT_1QBod5.jpg 800w, /_astro/visual.DoS9licT_Z2kQDra.jpg 980w, /_astro/visual.DoS9licT_1otTEj.jpg 1024w, /_astro/visual.DoS9licT_Z1peybd.jpg 1660w, /_astro/visual.DoS9licT_1stDrY.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Speakers on stage at css day" loading="eager" decoding="async" fetchpriority="auto" width="3590" height="2230" class="img-fluid"> </picture><p>This year, I did things a bit differently, by going to the <a href="https://www.iodigital.com/en" target="_blank" rel="noreferrer noopener">iO</a> offices in Amsterdam to get a full day of work in and meeting some of the people who I’ve only been talking to online for now. Meeting some awesome people such as <a href="https://www.davebitter.com/" target="_blank" rel="noreferrer noopener">Dave Bitter</a> and <a href="https://www.linkedin.com/in/sanderdejong88?originalSubdomain=nl" target="_blank" rel="noreferrer noopener">Sander de Jong</a>. Although I was very tired of a 3 hour drive (waking up around 5 a.m. to get there in time). It sure was fun to see them and walk around the Amsterdam office.</p> <picture> <source srcset="/_astro/dave-sander-me.BMrkLMJO_11gFJs.avif 320w, /_astro/dave-sander-me.BMrkLMJO_ZE7TNb.avif 480w, /_astro/dave-sander-me.BMrkLMJO_sn26l.avif 800w, /_astro/dave-sander-me.BMrkLMJO_Z2uDId.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/dave-sander-me.BMrkLMJO_Z1bQQVd.webp 320w, /_astro/dave-sander-me.BMrkLMJO_2cUFk5.webp 480w, /_astro/dave-sander-me.BMrkLMJO_Z1JKvzk.webp 800w, /_astro/dave-sander-me.BMrkLMJO_Z2fDcoS.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/dave-sander-me.BMrkLMJO_1vO2Au.jpg" srcset="/_astro/dave-sander-me.BMrkLMJO_Z15LvCB.jpg 320w, /_astro/dave-sander-me.BMrkLMJO_2j11CG.jpg 480w, /_astro/dave-sander-me.BMrkLMJO_Z1DFagI.jpg 800w, /_astro/dave-sander-me.BMrkLMJO_Z29xQ6h.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Dave, Sander and me" loading="lazy" decoding="async" fetchpriority="auto" width="2450" height="1840"> </picture> <p>But CSS Day did something different as well this year, starting off with a pre-conference event at the Kohnstammhuis in Amsterdam at 4 p.m. I was very late to the party and unfortunately missed all the talks there. Still It was great to join in with some of the speakers for a good chat. I was dead tired, so I was more listening than talking for the most part. But I was happy to join in with Adam Argyle and get some “Chrome swag”. The view of this building on the ninth flour alone was totally worth it to quickly hop over there!</p> <picture> <source srcset="/_astro/what-a-view-from-amsterdam.Co-6oBxK_ZaOnug.avif 320w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_Z1FyPnh.avif 480w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_C3KyL.avif 800w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_1RaYao.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/what-a-view-from-amsterdam.Co-6oBxK_IfMqE.webp 320w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_ZLtErm.webp 480w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_1w8VuG.webp 800w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_Z2iUXHC.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/what-a-view-from-amsterdam.Co-6oBxK_Z1D58qx.jpg" srcset="/_astro/what-a-view-from-amsterdam.Co-6oBxK_20RWy5.jpg 320w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_v8uF4.jpg 480w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_Z2gq2bO.jpg 800w, /_astro/what-a-view-from-amsterdam.Co-6oBxK_Z11iNAc.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Amsterdam skyline view from Kohnstammhuis" loading="lazy" decoding="async" fetchpriority="auto" width="1960" height="890"> </picture> <p>So, after a well-deserved good night’s sleep, it was time to start the conference. With <a href="https://nerdy.dev/" target="_blank" rel="noreferrer noopener">Adam Argyle</a> setting it off as the MC (CSS! CSS! CSS!). Last year, I met him as a speaker, and I kinda knew he would nail it as an MC as well. After a quick intro and announcing his colleague Una Kravets, it was time to start the show and dig in for CSS Day 2023!</p> <h2 id="una-kravets---state-of-the-css-community">Una Kravets - State of the CSS Community</h2> <p>Perfect start of the day with a quick recap of which new goodies are now available, spread across browsers and some of the things to look forward to. I’ve been following along with the newest trends in CSS for some time now but it still amazes me how much work is actually being done at the moment when it comes to making the language better. Someone actually asked me if this was “just a recap” for me and my answer was “hell no”. An interesting 40 minute talk showcasing feature after feature and some insiders on the new ideas around targeting state via container queries for position sticky (and potentially position-fallback?). It’s amazing how <a href="https://una.im/" target="_blank" rel="noreferrer noopener">Una</a> can explain these features so correctly and simply at the same time. Well done Una! Unfortunately, I didn’t take a picture of the “one less dependency” slide, But I was able to take a picture of all the features she didn’t get a chance to talk about, can you imagine? CSS is on fire!</p> <picture> <source srcset="/_astro/una.DNow1Lne_Z1H0VSj.avif 320w, /_astro/una.DNow1Lne_ZrTn2i.avif 480w, /_astro/una.DNow1Lne_Z1EOCaM.avif 800w, /_astro/una.DNow1Lne_8pkCV.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/una.DNow1Lne_Z1HzAt0.webp 320w, /_astro/una.DNow1Lne_Zst1BY.webp 480w, /_astro/una.DNow1Lne_Z1FogKt.webp 800w, /_astro/una.DNow1Lne_7PG3f.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/una.DNow1Lne_Z24tu2a.jpg" srcset="/_astro/una.DNow1Lne_PDeAp.jpg 320w, /_astro/una.DNow1Lne_25JNrq.jpg 480w, /_astro/una.DNow1Lne_ROyiV.jpg 800w, /_astro/una.DNow1Lne_Z2o7BGh.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Una at CSS-day" loading="lazy" decoding="async" fetchpriority="auto" width="2000" height="750"> </picture> <p>As an added side bonus we got to see the <a href="https://open.spotify.com/show/0iW21xFsrH509BGTEs3ufN" target="_blank" rel="noreferrer noopener">CSS podcast</a> live for a few minutes, how awesome is that? If you don’t know the show and you’re into CSS, you must check it out!</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/dkBeBxs48os" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="hidde-de-vries---dialogs--popovers">Hidde de Vries - Dialogs &amp; Popovers</h2> <p>I’ve met Hidde a few times as well during the <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">Open UI</a> telecoms so I was really excited for this one as well (He even helped me in reviewing one of my articles, thanks again for this mate!). When to use a dialog and when to use a popover will be a question that developers should be asking in the future. So I’m really glad that Hidde is taking it upon him to share some insights on when to choose which tool early on. With his background in accessibility and working for the Dutch government it was great to see these insights being shared. The popover UI will really change the way in how we handle some of our daily web UI in the future. UI considerations, Semantics and positioning will be key to defining which tool to use. When this gets shared on YouTube (I will of course update this article), you really should check it out! In the meantime, he wrote a bunch of great articles about it on <a href="https://hidde.blog/" target="_blank" rel="noreferrer noopener">hidde.blog</a>.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/XaO2mZnIOzs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="léonie-watson---css-speech">Léonie Watson - CSS Speech</h2> <p>I was really looking forward to this one and - it seems - rightly so. When we use CSS to style the web we’re always thinking about the visuals, but why is it that we can’t control the speech of content pages? I love the fact that she didn’t really go into the “aria of things” but rather about designing for speech such as choosing a voice on how a page should be read by assistive technology or choosing the tone and pitch. There are some great ideas there and most importantly, there used to be a spec for this that didn’t get enough attention. So now is the time! Maybe we should all think about the possibilities this could give in the future. So yes, share her article, comment on it, do whatever we can to make this possible ( <a href="https://tink.uk/why-we-need-css-speech/" target="_blank" rel="noreferrer noopener">https://tink.uk/why-we-need-css-speech/</a> ) I am skeptical on a corporate level on how we can take clients on board to invest extra time (money) for us to create these experience. Nevertheless, it would be nice if we can do it (or as a developer secretly put in the extra effort).</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/6X23I9yHqd4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="lunch-breaks-and-breaks-in-between">Lunch breaks and breaks in between</h2> <p>I haven’t really talked about this, but this version of CSS day gave us the chance to talk to some of the people working on Chrome DevTools and the even had a CSS helpdesk. Geeky as I am, I found it very refreshing and interesting to have a chat with <a href="https://twitter.com/ergunsh" target="_blank" rel="noreferrer noopener">Ergün Erdoğmuş</a>. Had a great little chat with him about the position-fallback possibilities and also talked a bit about animations later in the day with his team members from DevTools. Great opportunity, well done CSS day and Google! Maybe more on that soon in a future article ;)</p> <h2 id="sanne-t-hooft---tinkerer-by-night">Sanne ‘t Hooft - Tinkerer by Night</h2> <picture> <source srcset="/_astro/sanne.Auqq_mv2_2iSo9r.avif 320w, /_astro/sanne.Auqq_mv2_2aVvug.avif 480w, /_astro/sanne.Auqq_mv2_ZLfu30.avif 800w, /_astro/sanne.Auqq_mv2_Z22YCLE.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sanne.Auqq_mv2_Z1xL5gi.webp 320w, /_astro/sanne.Auqq_mv2_Z1FHWUt.webp 480w, /_astro/sanne.Auqq_mv2_rhalc.webp 800w, /_astro/sanne.Auqq_mv2_ZOrXns.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sanne.Auqq_mv2_Z43tyQ.jpg" srcset="/_astro/sanne.Auqq_mv2_Z1A8rYH.jpg 320w, /_astro/sanne.Auqq_mv2_Z1I5kDS.jpg 480w, /_astro/sanne.Auqq_mv2_oTMBM.jpg 800w, /_astro/sanne.Auqq_mv2_ZQOl6R.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Sanne at CSS-day" loading="lazy" decoding="async" fetchpriority="auto" width="2000" height="1500"> </picture> <p>And OMG what a Tinkerer he is. A”light talk” after lunch sprinkled with a lot of mathematics, but I wasn’t bored at all. Using techniques such as :has() and a minimum of JavaScript to replicate the Dock effect from a Mac. But more importantly, using a lot of trigonometric functions in CSS ( which I’m still really bad at) to create some amazing effects. It really became clear that we need <a href="https://github.com/w3c/csswg-drafts/issues/4559" target="_blank" rel="noreferrer noopener">something to count the number of children of an element</a> in this presentation. A great showcase of demos of which I took a lot of pictures and will need to study a bit more. I’m sure as hell going to geek off on this stuff in the next couple of weeks.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/oSzUeiYt_xU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="patrick-brosset---selector-performance">Patrick Brosset - Selector Performance</h2> <p>Performance is an important topic, so how about selector performance? Does using a wildcard (*) in your styles create some issues? Patrick already started his talk by telling us that there are probably a lot of other concerns that we will have to tackle before thinking about the performance of our CSS selectors. But all reassuring aside, there was an example on how it could potentially create some massive delays because of the changes that get triggered by using one selector. I especially enjoyed the explanation of the “purple bars of death” that are hard to fix when testing performance in DevTools (because they are about the browser and so - kinda - out of our hand. Some really interesting insights on how you can measure it. I’m not much of an Edge user but seeing some of these demos really makes me want to try some things out when it comes to testing performance and finding potential pitfalls. This talk was important, as we constantly try to make rules such as “this is bad for performance, don’t use it” while the truth is usually far more nuanced when it comes to being creative with selectors.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/WRiOWJZoKlw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="heydon-pickering---scope-in-css">Heydon Pickering - Scope in CSS</h2> <p>Why isn’t CSS scoped? Sounds like a crazy question right? CSS is always scoped but we just try to create global patterns as much as possible for consistency (as it should be). But hearing <a href="https://heydonworks.com/" target="_blank" rel="noreferrer noopener">Heydon</a> speak really showed how much he understood the daily struggles of a front-end developer vs some of the strategists, marketeers and designers, who seem to be evil by nature. This was a lighter talk which went hand in hand with a few laughs, but I really enjoy these as they create a sense of community. And all of the things were recognisable such as why we don’t just write CSS? Do we really need to use all these crazy things in order to have multiple <code>.button</code> classes and just fix that by some dirty hash getting placed right after it ( <code>.button_xlj23j</code> ). Writing CSS can be so simple and elegant and readable, but after showing us a bit of <code>@scope</code> , we all know that this will be a handy feature and change the way we write CSS. There were so many good speakers this year and I wouldn’t be able to pick a favorite, but for day 1, Heydon definitely wins the “slide of the day award” by showing how to create a global style in CSS versus Styled Components:</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/6S_kXfqH-u4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="sophie-koonin---personal-websites">Sophie Koonin - Personal Websites</h2> <p>Bring back the personal website! - Like -, yes! I took that jump a few years ago and really loved how it opened my mind and created some opportunities. And we should be proud about the personal stuff we create, I even bought myself some stickers with my website logo “just because”. She showed us her road from starting off with books to styling MySpace. I can really relate to this and especially loved the shout out to Neo Pets! Her talk definitely inspired me to create more personal stuff and maybe something just silly and stupid and place it on some free hosting. She also said that she wasn’t all that creative but don’t undersell yourself <a href="https://localghost.dev/" target="_blank" rel="noreferrer noopener">Sophie</a>, I really love the things you created and wish I had a bit more of those creative juices in my brain!</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/H2Ux0hGQcs4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="start-of-day-2">Start of day 2</h2> <p>The second day of CSS Day we were welcomed by a new MC: <a href="https://michellebarker.co.uk/" target="_blank" rel="noreferrer noopener">Michelle Barker</a>. Last year, I was happy to see a talk from here and now she’s the MC, there is a theme going on here. A bit of a different style of presenting than Adam, a bit more toned down but I just can’t help to love her enthusiasm and lovely British accent. She started of with a warm welcome, the general code of conduct and of course announcing the speaker, which was no one else but…</p> <h2 id="miriam-suzanne---container-queries">Miriam Suzanne - Container Queries</h2> <p>If you heard about Container Queries (or Susy, if you’re a fossil like me) you probably have heard about <a href="https://www.miriamsuzanne.com/" target="_blank" rel="noreferrer noopener">Miriam</a>. A spec writer and what a powerful speaker on the subject. Instead of just showing us container queries and how they worked, she took a different and in my opinion far better approach by telling us why things don’t work, or at least didn’t used to work. Some powerful insights here on how containment actually works and why it was so hard to get this spec going, why containers don’t have the data we need, or it’s just dangerous to get it out of them. Still in the end, things got rolling and we have them supported in all major browsers. That is, for container queries that calculate inline-size and for the future: Style queries are on the way (already available in Chrome for custom properties). Last but not least, in the future there might be state queries avialable, seeing if an element is “stuck” when using position sticky or checking which fallback is used in position-fallback. That last one for me (creating some tooltip demo’s for Open UI recently) is a feature I’m rooting for. She even showed us a little demo by my colleague from Eindhoven: <a href="https://mrtnvh.com/" target="_blank" rel="noreferrer noopener">Maarten Van Hoof</a>. It’s hard to keep myself from turning this into a technical article by hearing this talk. But as promised, this is just a small recap and I’m going to let all the things I heard sink in a bit further.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/-Fw8GSksUIo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="umar-hansa---tooling--workflows">Umar Hansa - Tooling &amp; Workflows</h2> <p>I recently <a href="https://utilitybend.com/blog/chrome-devtools-tricks-that-deserve-a-spotlight">did a shoutout</a> to the mailing list with DevTools tips by <a href="https://umaar.com/" target="_blank" rel="noreferrer noopener">Umar</a>. I knew I was in for a wild ride about tooling. Umar showed us so much fun stuff to increase our workflow from snippets in VSCode, to <a href="https://browsersync.io/" target="_blank" rel="noreferrer noopener">browsersync</a>, AI tools such as copilot and <a href="https://bloop.ai/" target="_blank" rel="noreferrer noopener">bloop</a>, <a href="https://chrome.google.com/webstore/detail/ghosttext/godiecgffnchndlihlpaajjcplehddca" target="_blank" rel="noreferrer noopener">ghosttext</a>, <a href="https://marketplace.visualstudio.com/items?itemName=wmaurer.vscode-jumpy" target="_blank" rel="noreferrer noopener">jumpy</a> and of course some awesome DevTools tricks. I will be checking out some of these tools in the upcoming weeks for sure! Yes, this might look like a small paragraph, especially for all the tools he shared with us. But oh my… let me do a bit of further research first, okay?</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/05VOZP-Uo0g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="stephanie-eckles---building-components">Stephanie Eckles - Building Components</h2> <p>How do we <a href="https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/" target="_blank" rel="noreferrer noopener">handle a reset in CSS in 2023</a>? There were some really powerful tips &amp; insights here especially when it came to creating a “general” sort of grid and flex layout, powered with custom properties and declaring a container on the html element. I noticed that some people were getting hungry for lunch, but honestly, I was still hungry for more. It was a bit more technical but very practical for the people who actually work on different projects on a day to day basis and I loved every bit of it. It’s a bit of a shame that I didn’t really get a chance to talk with her afterward, i would’ve loved to share some of my ideas around working with custom properties, oh well, maybe next time, but for now, I’ll surely <a href="https://twitter.com/5t3ph/" target="_blank" rel="noreferrer noopener">follow her on twitter</a>.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/Y50iqMlrqU8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="jhey-tompkins---creative-coding">Jhey Tompkins - Creative Coding</h2> <p>There is no way to describe all the cool creative demo’s from <a href="https://jhey.dev/" target="_blank" rel="noreferrer noopener">Jhey</a> in this article. To the CSS Day organization: Perfect fit for after lunch, just sit back and enjoy the ride. Also a part of the Open UI community and ex Google DevRel, he just shows some amazing stuff with :has(), form elements and of course popover and the selectmenu. I create some practical demo’s for the selectmenu which are semi-creative, but Jhey really just cranks the creativity of these things to 11. If you haven’t heard from him or viewed some of his work, you should definitely <a href="https://codepen.io/jh3y" target="_blank" rel="noreferrer noopener">check out his codepens</a>. Creating a lot of demos makes you better, creating “useless stuff” makes you think deeper and helps you understand how CSS works and that’s probably the main take on it. Keep practicing, stay creative and yeah, “just flip it”.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/N1kn5jsWPuw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="cassondra-roberts---styling-web-components">Cassondra Roberts - Styling Web Components</h2> <p><a href="https://twitter.com/castastrophee?lang=en" target="_blank" rel="noreferrer noopener">Cassondra’s</a> talk was a bit different, at least for me as I’m not the biggest user of web components out there. I loved how she presented her material with some “nerdy spacey jokes”, but the topic itself wasn’t something I use on a day to day basis. That being said, I really should be working on these web components a lot more often. Maybe this talk was just a bit too hard for me because of all the new things and the brain overload kicking in from two days into CSS day. But I did talk to some people later that day that really work a lot with web components and they seemed to have picked up a lot of things about it. One thing though, it made me want to learn more and when the videos come out, I’ll be sure to revisit.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/67bSCEEdaH8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="bramus-van-damme---scroll-driven-animations--view-transitions">Bramus Van Damme - Scroll-Driven Animations &amp; View Transitions</h2> <p>Pfoeh, this is hard… What can I say about <a href="https://www.bram.us/" target="_blank" rel="noreferrer noopener">Bramus</a>? He supported and motivated me so much for a year, I can’t thank him enough. He’s such a friendly guy, but let’s keep it about the things he brought to CSS Day. Taking over the View Transition talk from Jake Archibald and adding a bit of that scroll-driven animations sauce, we got two talks for the price of one. So let’s start with view transitions. This is something I haven’t explored enough, but such a powerful tool for creating immersive experiences. For the moment only available for single page applications and a more experimental implementation available for multi page applications (which is another fancy name for “websites”). I loved how he explained this topic so visually by showing us some 3D examples of the “pictures” that get taken from your DOM nodes and how they move while the transitioning is happening. Also clearly explaining how these things work so that we don’t try to do things like animating the color while the page is transitioning (that won’t work, it’s a screenshot, not a dom node anymore…). It really made the topic easy to understand and I’ll sure be playing around with it a bit more later on. He really can’t hide his past as a teacher and believe me, that’s a good thing! Understanding how these things work by showing it on a visual level is important.</p> <p>So, scroll driven animations… I really need to update my own demos on that spec (added to the todo list, and yes, he warned me on Twitter… and yes, I always forget to do it, my bad). It’s so interesting to see this spec is really growing up. At first, we just had the ability to change our animation from a duration standpoint to a scroll driven one, but now with the inclusion of timeline ranges, it’s really going to be a tool in our CSS bag to have “one less dependency” (cfr Una). If you want some more demo’s be sure to use Chrome Canary and visit some of his collection and tools at <a href="https://scroll-driven-animations.style/" target="_blank" rel="noreferrer noopener">scroll-driven-animation.style</a></p> <picture> <source srcset="/_astro/bramus.kPf6Ca_M_Z15yk2k.avif 320w, /_astro/bramus.kPf6Ca_M_Z24wUnz.avif 480w, /_astro/bramus.kPf6Ca_M_1X5SDv.avif 800w, /_astro/bramus.kPf6Ca_M_ZU4EDA.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/bramus.kPf6Ca_M_1bDbwe.webp 320w, /_astro/bramus.kPf6Ca_M_cEAaY.webp 480w, /_astro/bramus.kPf6Ca_M_ZOSIAR.webp 800w, /_astro/bramus.kPf6Ca_M_1m7PTX.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/bramus.kPf6Ca_M_Z4m2Ci.jpg" srcset="/_astro/bramus.kPf6Ca_M_ZIm2t.jpg 320w, /_astro/bramus.kPf6Ca_M_ZYGWnI.jpg 480w, /_astro/bramus.kPf6Ca_M_Z22ghaz.jpg 800w, /_astro/bramus.kPf6Ca_M_9Kilg.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Bramus at CSS-day" loading="lazy" decoding="async" fetchpriority="auto" width="2000" height="1500"> </picture> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/nFbuXdEU-oA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="manuel-matuzović---structuring--restructuring">Manuel Matuzović - Structuring &amp; Restructuring</h2> <p><a href="https://www.matuzo.at/" target="_blank" rel="noreferrer noopener">Manuel</a> was banned from Twitter and got a bit lost in all the new things happening in CSS, and who can blame him. There is A LOT of CSS. So he did a crazy thing, a 100 day challenge where each day he learns something new. I sometimes struggle to write one article each month so, massive respect for that!. But by the end of those 100 days he learned so much and showed us some of his favorite things about the new ways we can write out CSS. Using the power of custom properties and using inline styles to create some smart web component design systems. Even using the (only available in Chrome and Edge) style queries to create some sort of a Mixin. His talk was perfectly balanced between jokes and serious information and in my opinion was the perfect talk to end this year of CSS Day.</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/L668dK6wFcM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div> <h2 id="drinks-talking-and-a-summary">Drinks, talking and a summary</h2> <p>I had a blast talking to so many of the speakers and meeting some people IRL of the Open UI community, giving some thanks to people who reviewed some of my articles in the past. I learn a lot from these people and just being able to have a little chat with them is inspirational and just simply pleasant. Now if you reached the end of this article and haven’t yet yelled out to me saying “<strong>You didn’t actually show code or shared stuff in this article</strong>!” First of all let me thank you for reading this and I absolutely will be taking some inspiration from these talks for future articles and demos. But I need to process, and read a bit more on the subjects covered. Call this a “fanboy article”, or even “clickbait”, I really don’t care. But if you’re thinking: “Oh, CSS day seems cool, full of quality and I want to go”, then I achieved my goal here. Next year will mark the 10th edition of CSS Day. And last but not least a very special thank you to the organization once again! Thank you for providing some ice cream on the second day as it was hot as hell and very welcome. And as for me, I’ll do anything in my power to return to this amazing conference next year!</p> <p>( And now I need to run to the CSS Cafe post-conference event. On a Saturday? yep, i’m that geeky )</p> <div class="embed"><iframe width="560" height="315" src="https://www.youtube.com/embed/dRMwBpp2tQA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe></div>Brecht De RuyteAdvanced Form Control Styling With Selectmenu And Anchoring APIhttps://utilitybend.com/blog/advanced-form-control-styling-with-selectmenu-and-anchoring-api/https://utilitybend.com/blog/advanced-form-control-styling-with-selectmenu-and-anchoring-api/Thanks to the Open UI working community group, there's a new element on the horizon, the selectlist, that will make styling this type of form control a whole lot better. Let's use this element to creating a pattern that you would never have thought possible with CSS alone - a radial selection menu.Thu, 01 Jun 2023 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2023/06/advanced-form-control-styling-selectmenu-anchoring-api/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteThe button case - Using custom properties for a smart button systemhttps://utilitybend.com/blog/the-button-case-using-custom-properties-for-a-smart-button-system/https://utilitybend.com/blog/the-button-case-using-custom-properties-for-a-smart-button-system/Recently, I've created a presentation named “so, you're still not using custom properties?” In this article, I want to highlight a part of that presentation. Custom properties have been available to us for quite some time now. And creating a smart button design system is just one of the many things where they can shine.Wed, 24 May 2023 00:00:00 GMT<picture> <source srcset="/_astro/the-button-case.DI_13Mc7_XP3cl.avif 375w, /_astro/the-button-case.DI_13Mc7_Z23iwc5.avif 480w, /_astro/the-button-case.DI_13Mc7_18TWvy.avif 680w, /_astro/the-button-case.DI_13Mc7_1AYooP.avif 800w, /_astro/the-button-case.DI_13Mc7_1Rma10.avif 980w, /_astro/the-button-case.DI_13Mc7_IQQE.avif 1024w, /_astro/the-button-case.DI_13Mc7_Z2oQUrz.avif 1660w, /_astro/the-button-case.DI_13Mc7_Zsj9j0.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/the-button-case.DI_13Mc7_Z1zUbkl.webp 375w, /_astro/the-button-case.DI_13Mc7_s8n5a.webp 480w, /_astro/the-button-case.DI_13Mc7_Z1pPh18.webp 680w, /_astro/the-button-case.DI_13Mc7_ZWKP7Q.webp 800w, /_astro/the-button-case.DI_13Mc7_ZGo4vG.webp 980w, /_astro/the-button-case.DI_13Mc7_1HpMlg.webp 1024w, /_astro/the-button-case.DI_13Mc7_ZHaYWX.webp 1660w, /_astro/the-button-case.DI_13Mc7_1n06xs.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/the-button-case.DI_13Mc7_Z1hw76o.jpg" srcset="/_astro/the-button-case.DI_13Mc7_1uMD7s.jpg 375w, /_astro/the-button-case.DI_13Mc7_Z1wkVgX.jpg 480w, /_astro/the-button-case.DI_13Mc7_1ERxqF.jpg 680w, /_astro/the-button-case.DI_13Mc7_27VYjW.jpg 800w, /_astro/the-button-case.DI_13Mc7_2ojJV7.jpg 980w, /_astro/the-button-case.DI_13Mc7_YTFUp.jpg 1024w, /_astro/the-button-case.DI_13Mc7_Z1pG6nO.jpg 1660w, /_astro/the-button-case.DI_13Mc7_Zo0zje.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Layered buttons" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="a-bit-of-background-scss-vs-css">A bit of background: SCSS vs CSS</h2> <p>If you’re a front-end developer, chances are high that you’ve been using SASS or SCSS for quite some time. One of the things that really made an impact when SASS came along was the ability to use variables. CSS already has an alternative for this for quite some time in the form of custom properties, also known as “CSS3 variables”.</p> <p>A small comparison, if you would style the background your body tag in “hotpink” by using a SASS variable, it would look something like this:</p> <pre class="language-scss" data-language="scss"><code is:raw="" class="language-scss"><span class="token property"><span class="token variable">$color-primary</span></span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span> <span class="token selector">body </span><span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token variable">$color-primary</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>The same could be achieved by using CSS custom properties by writing the following:<br> <strong>Note:</strong> Custom properties are always prefixed by a double dash.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--color-primary</span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background</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> <p>There is quite a big difference here. Custom properties are something that the browser understands. Unlike SASS, which is a preprocessor, which means it will output a CSS file where your final code will look like the following if you use the SASS variable system:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>So, why am I telling you the basics? Why is this important?</p> <p>There is a lot more to custom properties than setting a variable in the <code>:root</code> and using it as a constant. The way custom properties are built, you can create some smart systems with them. A few of the benefits of using custom properties are the following:</p> <ul> <li>They can be scoped</li> <li>They can be overwritten</li> <li>They can have a fallback</li> <li>They can contain anything(!) you want</li> <li>They can be controlled with JS</li> </ul> <p>An <strong>important</strong> note is that they do impact the overall performance of your CSS. It’s not going to have a lot of impact by just adding a few colors to your root, but be mindful when using them that you don’t overdo it with heavy calculations or endless overwrites on a root level.</p> <p>That being said, it’s time to get to the main part of this article.</p> <h2 id="the-button-case---sass-variables-vs-css-custom-properties">The button case - SASS variables vs CSS custom properties</h2> <p>We will be styling buttons! 🥳 More specifically, these buttons:</p> <picture> <source srcset="/_astro/the-button-case-buttons.DPrO-H2U_1fETpf.avif 320w, /_astro/the-button-case-buttons.DPrO-H2U_MIwOR.avif 480w, /_astro/the-button-case-buttons.DPrO-H2U_qc43J.avif 800w, /_astro/the-button-case-buttons.DPrO-H2U_hDcgQ.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/the-button-case-buttons.DPrO-H2U_Rss8s.webp 320w, /_astro/the-button-case-buttons.DPrO-H2U_pw5y5.webp 480w, /_astro/the-button-case-buttons.DPrO-H2U_2YBLW.webp 800w, /_astro/the-button-case-buttons.DPrO-H2U_Z5yeYV.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/the-button-case-buttons.DPrO-H2U_Z19VWOR.png" srcset="/_astro/the-button-case-buttons.DPrO-H2U_Z1OqgnM.png 320w, /_astro/the-button-case-buttons.DPrO-H2U_Z2hmCXa.png 480w, /_astro/the-button-case-buttons.DPrO-H2U_2qi24D.png 800w, /_astro/the-button-case-buttons.DPrO-H2U_2hJahK.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="primary and secondary button and their outline variants" loading="lazy" decoding="async" fetchpriority="auto" width="2800" height="1500"> </picture> <p>Nothing mind blowing here, no fancy designs as well, just four buttons like we have seen on the web quite a few times, a primary and secondary button, who each have an outline variant. They all have a hover/focus state as well:</p> <picture> <source srcset="/_astro/the-button-case-buttons-hover.D61YyMye_egw6v.avif 320w, /_astro/the-button-case-buttons-hover.D61YyMye_2gFwK2.avif 480w, /_astro/the-button-case-buttons-hover.D61YyMye_1YnoP7.avif 800w, /_astro/the-button-case-buttons-hover.D61YyMye_ZiqYSW.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/the-button-case-buttons-hover.D61YyMye_Z1waWb3.webp 320w, /_astro/the-button-case-buttons-hover.D61YyMye_ve3st.webp 480w, /_astro/the-button-case-buttons-hover.D61YyMye_dUUxy.webp 800w, /_astro/the-button-case-buttons-hover.D61YyMye_Z23Stbv.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/the-button-case-buttons-hover.D61YyMye_Z1kbFro.png" srcset="/_astro/the-button-case-buttons-hover.D61YyMye_Z1j1QX.png 320w, /_astro/the-button-case-buttons-hover.D61YyMye_215XLy.png 480w, /_astro/the-button-case-buttons-hover.D61YyMye_1IMPQD.png 800w, /_astro/the-button-case-buttons-hover.D61YyMye_Zy1xRq.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="primary and secondary button and their outline variants on hover" loading="lazy" decoding="async" fetchpriority="auto" width="2800" height="1500"> </picture> <p>For the sake of making my point in this article, the buttons have some basic styling for padding and border-radius that I won’t be repeating for the rest of this article:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 13px 20px<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 1.1rem<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 punctuation">}</span> </code></pre> <h3 id="using-sass-variables-to-style-our-buttons">Using SASS variables to style our buttons</h3> <p>When using SASS variables for coloring our buttons, we’d probably use mixin of some sorts. But as this is not a “SASS tutorial”, I want to talk about the output it creates. If we would create these buttons by using SASS variables, the coloring of them would grant you the following output (big chunk of code coming up):</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 2px solid 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 selector">button:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> DarkCyan<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> DarkCyan<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">background</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button.secondary:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button.outline</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<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> <span class="token selector">button.outline:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">border-color</span><span class="token punctuation">:</span> DarkCyan<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> <span class="token selector">button.outline.secondary</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> deeppink <span class="token punctuation">}</span> <span class="token selector">button.outline.secondary:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>So although we might’ve used something smart such as a mixin, by using SCSS variables, our processed code looks very repetitive by constantly overwriting the colors and getting that specificity just right. We can do better! So let’s start fresh and apply the same styles, but this time by using custom properties. <a href="https://codepen.io/utilitybend/pen/JjmKWKb" target="_blank" rel="noreferrer noopener">You can view this output example on CodePen</a>.</p> <h3 id="using-custom-properties-to-style-our-buttons">Using custom properties to style our buttons</h3> <p>Let’s rewrite this by just using CSS instead of a preprocessor. If we look at our buttons, we notice they have one thing in common, they change color on hover. So let’s start using custom properties to create a smart system for our “filled buttons”</p> <pre class="language-css" data-language="css"><code is:raw="" 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> black<span class="token punctuation">;</span> <span class="token property">background</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 property">border</span><span class="token punctuation">:</span> 2px solid <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 property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button:is(:hover, :focus)</span> <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> <span class="token selector">button.secondary</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button.secondary:is(:hover, :focus)</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>What did we just do? We created a custom property named <code>--color</code> and scoped this to our button element. Then we updated the background- and border color to read these custom properties. As custom properties can be overwritten, we will just update the custom property instead of re-declaring our border and background. This reduces our CSS size significantly.</p> <p>We haven’t introduced a fallback yet, which in this case might not be needed, but for tutorial sake, let’s do this. Let’s update our default button by making it read the <code>--color</code> variable and fallback to the default color when it isn’t available. Instead of adding a fallback to each usage of <code>var()</code>, we can use a single custom property to handle that. I will prefix that custom property with an underscore for now, this is just a convention as it was used to indicate private variables in JavaScript before <code>const</code> and <code>let</code> were a thing (ah, the good old days). Feel free to indicate these things in a way that fits your style. Updated part of the code:</p> <pre class="language-css" data-language="css"><code is:raw="" 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>--color<span class="token punctuation">,</span> black<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background</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 property">border</span><span class="token punctuation">:</span> 2px solid <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 property">color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Great! Now all we need to do is create our outline version of our buttons. We already have all the tools in play by using custom properties for our filled buttons, the only difference our outline buttons have is that they have a transparent background and change text color instead of background color. Now we can really take a shortcut to make this happen:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button.outline</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<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>We have now styled our 4 buttons! Reducing our code significantly. Including white spaces, we started out with about 40 lines of code and by using custom properties, we turned that down to 19, which is more than a 50% reduction. Here is the version we just created:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/LYgZWdz?default-tab=result&editable=true&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="the-button-case---enhanced-usage-of-custom-properties">The button case - enhanced usage of custom properties</h2> <p>If you’re like me, you don’t give up easily. I was wondering if we could somehow even make our code smarter. There is even a smarter way to create our four buttons by introducing a second custom property for the hover and focus color. Reducing our code to 17 lines and making new color variants even less redundant. This is the full code:</p> <pre class="language-css" data-language="css"><code is:raw="" 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>--color<span class="token punctuation">,</span> black<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background</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 property">border</span><span class="token punctuation">:</span> 2px solid <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 property">color</span><span class="token punctuation">:</span> white<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">--color</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token property">--hoverColor</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">button.outline</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<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 selector">button:is(:hover, :focus)</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>--hoverColor<span class="token punctuation">,</span> DarkCyan<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>So instead of re-declaring our hover states, we added a general hover for our button element which reads the <code>--hoverColor</code> custom property and has a fallback to the default button hover state.</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/yLRJorO?default-tab=result&editable=true&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <p>This does come with some up- and downsides which we need to address.</p> <h3 id="on-the-upside">On the upside…</h3> <p>If we would style our buttons like this, we could easily create a <strong>new color variant that both has a filled and outline version</strong> of the button. For example, if I wanted to add a new orange variant I could add the following next to the secondary button:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button.orange</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> orange<span class="token punctuation">;</span> <span class="token property">--hoverColor</span><span class="token punctuation">:</span> darkorange<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="and-the-downside">And the downside…</h2> <p>We are getting in a bit of a specificity danger zone here as the hover state of the button needs to come last in our stylesheet. There is a way to prevent this by adding our basic button styles inside of a <strong>cascade layer</strong>. Whether this is worth the effort really depends on how much of these buttons you’re going to style and is completely subjective. The reason I’m showing these different methods is that you have a choice and carefully plan the approach which suits you best.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* default buttons layer */</span> <span class="token atrule"><span class="token rule">@layer</span> buttons</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>--color<span class="token punctuation">,</span> black<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">background</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 property">border</span><span class="token punctuation">:</span> 2px solid <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 property">color</span><span class="token punctuation">:</span> white<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">--color</span><span class="token punctuation">:</span> deeppink<span class="token punctuation">;</span> <span class="token property">--hoverColor</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">/* Non layered styles come last */</span> <span class="token selector">button.outline</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> transparent<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 selector">button:is(:hover, :focus)</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>--hoverColor<span class="token punctuation">,</span> DarkCyan<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* then in another file or lower in the file, you can add them to the layer */</span> <span class="token atrule"><span class="token rule">@layer</span> buttons</span> <span class="token punctuation">{</span> <span class="token selector">button.orange</span> <span class="token punctuation">{</span> <span class="token property">--color</span><span class="token punctuation">:</span> orange<span class="token punctuation">;</span> <span class="token property">--hoverColor</span><span class="token punctuation">:</span> darkorange<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/eYPbYYK?default-tab=css%2Cresult&editable=true&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="so-youre-still-not-using-custom-properties">So, you’re still not using custom properties?</h2> <p>I think custom properties can really help us in creating some smart design systems. And if you haven’t been using them a lot, I completely understand. I was late to the party as well…<br> But once you start to grow a habit of using them, it’s like a completely new world opens up into writing CSS. So I would strongly encourage you to play around with them. In my presentation, I go over a lot more ways on how to use them, including progressive enhancements and even using them as booleans by adding container style queries and <code>:has()</code> into the mix. They can greatly benefit our workflow by writing smart code that does more with less. I hope this little button case really helped you into “thinking custom properties” and will enhance your upcoming project.</p> <p>I’m sure I’ll see some more awesome tricks involving custom properties during the course of <a href="https://cssday.nl/2023" target="_blank" rel="noreferrer noopener">CSS-day</a> which I will be attending next month, can’t wait! 🥳</p>Brecht De RuyteChrome DevTools tricks that deserve a spotlighthttps://utilitybend.com/blog/chrome-devtools-tricks-that-deserve-a-spotlight/https://utilitybend.com/blog/chrome-devtools-tricks-that-deserve-a-spotlight/A lot of developers use Chrome as their main browser and I'm one of them. There are a few tricks in Chrome DevTools that don't seem to get enough attention. So for this article, I wanted to highlight some of the features that I love and actually use in DevTools, that some of you might not know about.Tue, 25 Apr 2023 00:00:00 GMT<picture> <source srcset="/_astro/chrome-devtools-visual.COF3EWpV_16nl9P.avif 375w, /_astro/chrome-devtools-visual.COF3EWpV_Z1pV7ML.avif 480w, /_astro/chrome-devtools-visual.COF3EWpV_1Jn5BV.avif 680w, /_astro/chrome-devtools-visual.COF3EWpV_Z14atzy.avif 800w, /_astro/chrome-devtools-visual.COF3EWpV_ZcB1NH.avif 980w, /_astro/chrome-devtools-visual.COF3EWpV_Z1uBbgm.avif 1024w, /_astro/chrome-devtools-visual.COF3EWpV_ZjtFMw.avif 1660w, /_astro/chrome-devtools-visual.COF3EWpV_9mR2g.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/chrome-devtools-visual.COF3EWpV_IaSS3.webp 375w, /_astro/chrome-devtools-visual.COF3EWpV_Z1N8z4y.webp 480w, /_astro/chrome-devtools-visual.COF3EWpV_1maDl9.webp 680w, /_astro/chrome-devtools-visual.COF3EWpV_Z1rmUQl.webp 800w, /_astro/chrome-devtools-visual.COF3EWpV_ZzNt5u.webp 980w, /_astro/chrome-devtools-visual.COF3EWpV_1WEnE3.webp 1024w, /_astro/chrome-devtools-visual.COF3EWpV_Z1VpfG3.webp 1660w, /_astro/chrome-devtools-visual.COF3EWpV_IBzvl.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/chrome-devtools-visual.COF3EWpV_Z1VqQzX.jpg" srcset="/_astro/chrome-devtools-visual.COF3EWpV_2uVVrH.jpg 375w, /_astro/chrome-devtools-visual.COF3EWpV_Z1mwuT.jpg 480w, /_astro/chrome-devtools-visual.COF3EWpV_Z1VfrT8.jpg 680w, /_astro/chrome-devtools-visual.COF3EWpV_ko6Hj.jpg 800w, /_astro/chrome-devtools-visual.COF3EWpV_1bWyta.jpg 980w, /_astro/chrome-devtools-visual.COF3EWpV_1in6g6.jpg 1024w, /_astro/chrome-devtools-visual.COF3EWpV_2tuAIV.jpg 1660w, /_astro/chrome-devtools-visual.COF3EWpV_ACjBB.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Chrome Devtools logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>A bit of a shorter article for this month, because I’ve been pretty busy with creating presentations and writing some articles as a guest writer. Nevertheless, I always wanted to write something about Chrome DevTools features. I try to keep myself from writing articles with clickbait titles such as: “5 tools that…”, “6 amazing …”, but to be honest, this is kind of such an article, sorry about that. Still I hope to give you something here you didn’t know about, or want to revisit. So let the list begin!</p> <p>I will be using the shortcut <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> (or <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>p</kbd>) a lot. This is probably the most important shortcut for DevTools, so you best remember it.</p> <h2 id="taking-a-screenshot-from-an-area-full-page-or-node">Taking a screenshot from an area, (full) page, or node</h2> <p>This one only recently came to my attention by following <a href="https://umaar.com/" target="_blank" rel="noreferrer noopener">Umar Hansa</a>. I’ve been subscribed to his mailing list for quite some time now and really learned a lot from his little DevTools tips in the past. Seems like he started sending those emails again after a break, that’s nice to see.</p> <p>At <a href="https://www.iodigital.com/en" target="_blank" rel="noreferrer noopener">iO</a>, when we do a lot of visual changes to a project, it’s nice to add a screenshot of the update inside of a pull request. And I really love the node screenshot capture for this. It’s really easy, but not that many people know about it, so this is how you do that:</p> <p>With DevTools open, press <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> and start typing “screenshot”, You already see the 4 options here:</p> <ul> <li><strong>Capture area screenshot</strong>: gives you a cursor that allows you to take a screenshot of a selected area.</li> <li><strong>Capture full size screenshot</strong>: Take a screenshot of the complete page (full width / height</li> <li><strong>Capture node screenshot</strong>: Take a screenshot of the selected node you have selected in DevTools</li> <li><strong>Capture screenshot</strong>: Take a screenshot of the window</li> </ul> <figure class="thirds image"><picture> <source srcset="/_astro/devtools-screenshot-option.DF19w5m1_1bo7Pn.avif 320w, /_astro/devtools-screenshot-option.DF19w5m1_Z10nb1J.avif 480w, /_astro/devtools-screenshot-option.DF19w5m1_1fB4O3.avif 800w, /_astro/devtools-screenshot-option.DF19w5m1_23yqr6.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-screenshot-option.DF19w5m1_Z1eUlDO.webp 320w, /_astro/devtools-screenshot-option.DF19w5m1_1Duti0.webp 480w, /_astro/devtools-screenshot-option.DF19w5m1_Z1aHoF9.webp 800w, /_astro/devtools-screenshot-option.DF19w5m1_ZmK336.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-screenshot-option.DF19w5m1_2pnGTK.png" srcset="/_astro/devtools-screenshot-option.DF19w5m1_1xdoj2.png 320w, /_astro/devtools-screenshot-option.DF19w5m1_ZDxTy5.png 480w, /_astro/devtools-screenshot-option.DF19w5m1_1BqlhH.png 800w, /_astro/devtools-screenshot-option.DF19w5m1_2pnGTK.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Take a screenshot in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="500"> </picture></figure> <p>The node option really gives you such a clean screenshot in cases you want to share something with others about a specific element on the page.</p> <h2 id="the-rendering-drawer-and-toggling-preferences">The rendering drawer and toggling preferences</h2> <p>Chances are that the rendering drawer is not yet visible on your DevTools. You can get it easily by pressing <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> and start typing “rendering”. Select the first hit, this should open the drawer:</p> <figure class="half image"><picture> <source srcset="/_astro/open-rendering-drawer.Z2GN2UKG_2rfDax.avif 320w, /_astro/open-rendering-drawer.Z2GN2UKG_Z1PWmfD.avif 480w, /_astro/open-rendering-drawer.Z2GN2UKG_ZRAfYz.avif 593w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/open-rendering-drawer.Z2GN2UKG_2dobJt.webp 320w, /_astro/open-rendering-drawer.Z2GN2UKG_Z24NNFH.webp 480w, /_astro/open-rendering-drawer.Z2GN2UKG_Z16rHpD.webp 593w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/open-rendering-drawer.Z2GN2UKG_R2809.png" srcset="/_astro/open-rendering-drawer.Z2GN2UKG_1wdr66.png 320w, /_astro/open-rendering-drawer.Z2GN2UKG_2jcztQ.png 480w, /_astro/open-rendering-drawer.Z2GN2UKG_Z1MCs41.png 593w" sizes="(max-width: 800px) 100vw, 80vw" alt="Open rendering drawer in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="670"> </picture></figure> <p>There are a lot of things in the rendering drawer that come in handy for debugging or optimizing your project. I won’t be going in-depth for all of the items here as this drawer could be an article by itself, but let me highlight some of the basics:</p> <h3 id="detection-of-potential-rendering-issues">Detection of potential rendering issues</h3> <p>The rendering drawer is divided in some sections with a border and the first part is handy for debugging. The ones that I like to use here are the first three (convenient):</p> <ul> <li><strong>Paint flashing</strong> to highlight the repaints</li> <li><strong>Layout shift regions</strong> in case layout shifts are detected</li> <li><strong>Layer borders</strong> can be handy as well when debugging those z-indexes</li> </ul> <p>I haven’t really used the others a lot, but I do think they can be handy depending on the kind of work you do. I think there was a case where I could’ve used the “disable local fonts” feature in the past. There was a case where the client got some bad version of the font, but I didn’t know about this drawer then. But if it ever happens again, I’m glad to know I have something like this in the toolbox.</p> <h3 id="emulate-media-features">Emulate media features</h3> <p>This is the part of the rendering drawer that I use the most. It has some options to switch between user preferences such as light and dark mode, reduced motion and prefers-contrast. There is already an option to check for the reduced-data media feature, although there isn’t much support for it yet, something I think I’ll use a lot more in the future. There also seems to be a new one which forces the color-gamut that I have yet to play around with.</p> <p>The benefit of this, is that the options stay active even while reloading the page.</p> <figure class="thirds image"><picture> <source srcset="/_astro/rendering-drawer-emulation.DhHt-YbZ_fai7K.avif 320w, /_astro/rendering-drawer-emulation.DhHt-YbZ_Z1VB0Jm.avif 480w, /_astro/rendering-drawer-emulation.DhHt-YbZ_jnf6q.avif 800w, /_astro/rendering-drawer-emulation.DhHt-YbZ_17kAIt.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/rendering-drawer-emulation.DhHt-YbZ_Z2b9bmr.webp 320w, /_astro/rendering-drawer-emulation.DhHt-YbZ_HgDzn.webp 480w, /_astro/rendering-drawer-emulation.DhHt-YbZ_Z26VenL.webp 800w, /_astro/rendering-drawer-emulation.DhHt-YbZ_Z1iXRKI.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/rendering-drawer-emulation.DhHt-YbZ_1t9Rc8.png" srcset="/_astro/rendering-drawer-emulation.DhHt-YbZ_AYyAp.png 320w, /_astro/rendering-drawer-emulation.DhHt-YbZ_Z1zLJgH.png 480w, /_astro/rendering-drawer-emulation.DhHt-YbZ_Fcvz5.png 800w, /_astro/rendering-drawer-emulation.DhHt-YbZ_1t9Rc8.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Rendering drawer - emulation options in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="500"> </picture></figure> <p>It’s nice and easy to toggle these media features while optimizing your project for accessibility and I really suggest people taking a look at these features.</p> <p><strong>Bonus tip</strong>: Once you know the options available here you can use <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> and start typing the rendering feature you want for easy access. I often just type in “reduce” to toggle the motion setting when creating animations.</p> <figure class="half image"><picture> <source srcset="/_astro/run-animation-preference-devtools.BwhPqmgj_2oA2oG.avif 320w, /_astro/run-animation-preference-devtools.BwhPqmgj_Z101qt2.avif 480w, /_astro/run-animation-preference-devtools.BwhPqmgj_Z16zMkN.avif 593w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/run-animation-preference-devtools.BwhPqmgj_xljSd.webp 320w, /_astro/run-animation-preference-devtools.BwhPqmgj_2dUYOq.webp 480w, /_astro/run-animation-preference-devtools.BwhPqmgj_27mCWE.webp 593w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/run-animation-preference-devtools.BwhPqmgj_Zo5oCJ.png" srcset="/_astro/run-animation-preference-devtools.BwhPqmgj_ZpLiol.png 320w, /_astro/run-animation-preference-devtools.BwhPqmgj_1fNmwR.png 480w, /_astro/run-animation-preference-devtools.BwhPqmgj_19f0F6.png 593w" sizes="(max-width: 800px) 100vw, 80vw" alt="Fast access to animation preference in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="312"> </picture></figure> <h3 id="checking-for-visual-deficiencies-and-disable-image-formats">Checking for visual deficiencies and disable image formats</h3> <p>There is an option to check for visual deficiencies which will “re-color” your project. There are so many types of small deficiencies, sometimes it’s nice to know if a certain type of color blindness could really mess up the experience. Haven’t used it a lot yet (mostly because I don’t design most of the projects I create), but it’s nice to have it when needed.</p> <p>The last two options in this drawer are to disable the AVIF and WebP image format. If there are some problems because of the lack of next-gen image format support, you don’t have to leave Chrome at all and just toggle this setting.</p> <h2 id="the-color-picker">The color picker</h2> <p>This little panel you have when clicking a color inside of your styles inspector has so many options. I’m still finding new ones from time to time. This is so much more than a single color picker. Here is what it looks like:</p> <figure class="half image small-image"><picture> <source srcset="/_astro/color-panel-devtools.BkRoJA9a_Z7CLqE.avif 269w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/color-panel-devtools.BkRoJA9a_10WGdi.webp 269w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/color-panel-devtools.BkRoJA9a_Zjrqsp.png" srcset="/_astro/color-panel-devtools.BkRoJA9a_Zjrqsp.png 269w" sizes="(max-width: 800px) 100vw, 80vw" alt="Color panel with hinting in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="269" height="525"> </picture></figure> <p>You can really fine-tune your contrast as it gives you some hinting on which color you need to get the best contrast. It even shows some lines inside the color picker to show you where those potential contrast improvements are. I wrote an article about this at the start of this year for Smashing Magazine: <a href="https://www.smashingmagazine.com/2023/01/creating-high-contrast-design-system-css-custom-properties/" target="_blank" rel="noreferrer noopener">Creating A High-Contrast Design System With CSS And Custom Properties</a> if you want to learn more about working with this panel for contrast.</p> <h3 id="color-variables-and-color-formats">Color variables and Color formats</h3> <p>If you use custom properties for your colors (which, in my humble opinion, you should) You can easily replace your colors with already defined custom properties inside of the inspector. In the swatches you see at the bottom, you can click the double arrows and select css-variables. This will provide you with swatches based on your custom properties and it will actually enter that variable when you select the swatch, pretty neat if you ask me! You can also filter on some other swatches including colors that were found by the devtools on your project in general.</p> <p>With the new color formats entering CSS you also get some great visual hinting when selecting an HD format. For example when selecting LAB, you get to see which colors fall out of the sRGB spectrum and benefit from the HD color gamut. Aside from a few little personal demo’s, I still need to take a deeper dive into these new color formats, but it’s nice to know that DevTools can help me with this.</p> <figure class="half image small-image"><picture> <source srcset="/_astro/hd-color-panel-devtools.C0UH7_Rm_Z1WGqcq.avif 276w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/hd-color-panel-devtools.C0UH7_Rm_Z1DqiDg.webp 276w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/hd-color-panel-devtools.C0UH7_Rm_ZTpS6q.png" srcset="/_astro/hd-color-panel-devtools.C0UH7_Rm_4jweN.png 276w" sizes="(max-width: 800px) 100vw, 80vw" alt="HD color panel with hinting in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="350" height="540"> </picture></figure> <h2 id="a-full-page-accessibility-tree-in-devtools">A full page accessibility tree in DevTools</h2> <p>This feature is still experimental for the moment. But it’s one that I’ve enabled as it can give me a nice overview of my landmarks and general structure for screen readers. I still believe that great testing involves actually using a screen reader and a person who actually uses them, but aside from that, this is a great starting point.</p> <p>To enable this, go to “accessibility” inside of the elements panel (or use <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> and type accessibility) and enable the little checkbox “Enable full-page accessibility tree”. You will have to reload DevTools after that.</p> <figure class="thirds image"><picture> <source srcset="/_astro/devtools-enable-accessibility.DlVWf4Ax_26mAqc.avif 320w, /_astro/devtools-enable-accessibility.DlVWf4Ax_Z6qx5j.avif 480w, /_astro/devtools-enable-accessibility.DlVWf4Ax_1w9WOE.avif 800w, /_astro/devtools-enable-accessibility.DlVWf4Ax_2d2NQn.avif 837w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-enable-accessibility.DlVWf4Ax_ZpbHxW.webp 320w, /_astro/devtools-enable-accessibility.DlVWf4Ax_2schJt.webp 480w, /_astro/devtools-enable-accessibility.DlVWf4Ax_ZYol9u.webp 800w, /_astro/devtools-enable-accessibility.DlVWf4Ax_Zivu7L.webp 837w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-enable-accessibility.DlVWf4Ax_1saCmb.png" srcset="/_astro/devtools-enable-accessibility.DlVWf4Ax_7MecO.png 320w, /_astro/devtools-enable-accessibility.DlVWf4Ax_Z250TiG.png 480w, /_astro/devtools-enable-accessibility.DlVWf4Ax_ZrponI.png 800w, /_astro/devtools-enable-accessibility.DlVWf4Ax_esrD0.png 837w" sizes="(max-width: 800px) 100vw, 80vw" alt="Enable accessibility in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="821" height="341"> </picture></figure> <p>When enabled, you get a little accessibility Icon in the top right corner of your elements which you can toggle to see the full-page accessibility tree. I don’t consider myself an accessibility expert, but I do my best and keep learning so this can help a lot.</p> <figure class="thirds image"><picture> <source srcset="/_astro/accessibility-tree-devtools.xkuVi9Kh_Z1s9YWc.avif 320w, /_astro/accessibility-tree-devtools.xkuVi9Kh_Z28CDdC.avif 480w, /_astro/accessibility-tree-devtools.xkuVi9Kh_ZRNmGH.avif 800w, /_astro/accessibility-tree-devtools.xkuVi9Kh_1HqM1Q.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/accessibility-tree-devtools.xkuVi9Kh_Zy4O1h.webp 320w, /_astro/accessibility-tree-devtools.xkuVi9Kh_Z1exshH.webp 480w, /_astro/accessibility-tree-devtools.xkuVi9Kh_1gNed.webp 800w, /_astro/accessibility-tree-devtools.xkuVi9Kh_Z2sFaQa.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/accessibility-tree-devtools.xkuVi9Kh_21rA2s.png" srcset="/_astro/accessibility-tree-devtools.xkuVi9Kh_Zx0U1J.png 320w, /_astro/accessibility-tree-devtools.xkuVi9Kh_Z1dtyia.png 480w, /_astro/accessibility-tree-devtools.xkuVi9Kh_2kHdK.png 800w, /_astro/accessibility-tree-devtools.xkuVi9Kh_Z2rBgQC.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Accessibility tree in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="821" height="295"> </picture></figure> <h2 id="css-overview">CSS overview</h2> <p>Press <kbd>cmd</kbd>+<kbd>shift</kbd>+<kbd>p</kbd> and type in “CSS overview”. This is quite a handy panel to go to to see some information at first glance. It’s a great tool to spot potential contrast issues inside of the colors tab, find font information or even spot unused declarations. This is great in cases where you want to optimize your CSS as well by reducing the amount of media queries. I love sniffing around in that panel when I visit certain websites as well… I know, that might be a bit too geeky. I also have some things for my own website here that I really want to take care of that seems to get moved to my personal backlog all the time.</p> <figure class="thirds image"><picture> <source srcset="/_astro/devtools-css-overview.DobVZWKf_2tducD.avif 320w, /_astro/devtools-css-overview.DobVZWKf_2f7Le2.avif 480w, /_astro/devtools-css-overview.DobVZWKf_ZXO53V.avif 800w, /_astro/devtools-css-overview.DobVZWKf_ZzuNoG.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/devtools-css-overview.DobVZWKf_vKGLx.webp 320w, /_astro/devtools-css-overview.DobVZWKf_hEXMV.webp 480w, /_astro/devtools-css-overview.DobVZWKf_28UgjT.webp 800w, /_astro/devtools-css-overview.DobVZWKf_Z2wWAOM.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/devtools-css-overview.DobVZWKf_ZPdEON.png" srcset="/_astro/devtools-css-overview.DobVZWKf_ZySTWp.png 320w, /_astro/devtools-css-overview.DobVZWKf_ZMYCV1.png 480w, /_astro/devtools-css-overview.DobVZWKf_13fDzW.png 800w, /_astro/devtools-css-overview.DobVZWKf_1ryUfc.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="CSS overview in Chrome DevTools" loading="lazy" decoding="async" fetchpriority="auto" width="821" height="374"> </picture></figure> <h2 id="devtools-has-so-much-more-that-i-dont-know-about">DevTools has so much more that I don’t know about</h2> <p>I really discover something new in DevTools at least monthly because it gets developed so actively. For example, I recently saw <a href="https://twitter.com/bramus/status/1647955337339715586" target="_blank" rel="noreferrer noopener">Bramus tweeting a specificity feature that is in the works</a>. We use this in our toolkit on a daily basis and it has so much to offer. It’s a personal goal to become a bit more of a “power user”. If you want to learn more, I’d like to end this article with some of the places I frequently find new information on the subject:</p> <ul> <li> <a href="https://umaar.com/" target="_blank" rel="noreferrer noopener">Umar Hansa email tips</a> </li> <li> <a href="https://developer.chrome.com/tags/devtools/" target="_blank" rel="noreferrer noopener">Google DevTools Blog</a> </li> <li> <a href="https://www.youtube.com/@ChromeDevs" target="_blank" rel="noreferrer noopener">Google Chrome Developers YouTube channel</a> </li> <li>The <a href="https://open.spotify.com/show/0iW21xFsrH509BGTEs3ufN" target="_blank" rel="noreferrer noopener">CSS podcast</a> has some great featured episodes on DevTools as well with Jecelyn as a guest.</li> </ul> <p><strong>Note:</strong> the CSS podcast link is for Spotify, but you can find some episodes on YouTube as well. I think I heard every episode in my car… That way people don’t bust me laughing out loud with some of the (maybe too) geeky jokes on the show.</p>Brecht De RuyteOpen UI and the Popover APIhttps://utilitybend.com/blog/open-ui-and-the-popover-api/https://utilitybend.com/blog/open-ui-and-the-popover-api/If it pops, it belongs in HTML and CSS! Lately, I had a lot of fun playing around with this upcoming feature. The first version is being developed and it's starting to look pretty good. It's time to handle our basic popovers without the explicit need of JavaScript.Tue, 21 Mar 2023 00:00:00 GMT<picture> <source srcset="/_astro/visual-open-ui.dM0wIHbq_xaJDf.avif 375w, /_astro/visual-open-ui.dM0wIHbq_Z2reFI2.avif 480w, /_astro/visual-open-ui.dM0wIHbq_Z2qWO9o.avif 680w, /_astro/visual-open-ui.dM0wIHbq_Z2s4yWY.avif 800w, /_astro/visual-open-ui.dM0wIHbq_26BRti.avif 980w, /_astro/visual-open-ui.dM0wIHbq_ZoGoVJ.avif 1024w, /_astro/visual-open-ui.dM0wIHbq_ZDLcBJ.avif 1660w, /_astro/visual-open-ui.dM0wIHbq_1567Eg.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-open-ui.dM0wIHbq_Z1EWN2q.webp 375w, /_astro/visual-open-ui.dM0wIHbq_pNTpe.webp 480w, /_astro/visual-open-ui.dM0wIHbq_q5KXR.webp 680w, /_astro/visual-open-ui.dM0wIHbq_oY1ah.webp 800w, /_astro/visual-open-ui.dM0wIHbq_Z6vFcn.webp 980w, /_astro/visual-open-ui.dM0wIHbq_26Jukv.webp 1024w, /_astro/visual-open-ui.dM0wIHbq_1QEGEv.webp 1660w, /_astro/visual-open-ui.dM0wIHbq_Z2ip5F4.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-open-ui.dM0wIHbq_Z3LWWX.jpg" srcset="/_astro/visual-open-ui.dM0wIHbq_Z1yRrIO.jpg 375w, /_astro/visual-open-ui.dM0wIHbq_vTfHP.jpg 480w, /_astro/visual-open-ui.dM0wIHbq_wb7ht.jpg 680w, /_astro/visual-open-ui.dM0wIHbq_v4msS.jpg 800w, /_astro/visual-open-ui.dM0wIHbq_ZqjSL.jpg 980w, /_astro/visual-open-ui.dM0wIHbq_7gaXn.jpg 1024w, /_astro/visual-open-ui.dM0wIHbq_Z7NBGC.jpg 1660w, /_astro/visual-open-ui.dM0wIHbq_24gVI1.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Open UI logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture> <p>I always wanted to learn about W3C communities and since the end of 2022, I started to get involved with Open UI. I contribute by creating demos, trying things out, listening closely to the meetings and following up <a href="https://github.com/openui/open-ui" target="_blank" rel="noreferrer noopener">the GitHub repo</a>. It’s been a very interesting learning experience and I hope to do a bit more in the future with this awesome W3C community.</p> <p>To spread the word a bit about the cool things this group is achieving, I thought I’d write this little article with a bit of information on the group itself and the basics of the Popover API. I’m planning to write more about this, so you might call this: “the first of a series”.</p> <h2 id="what-is-open-ui">What is Open UI?</h2> <p><a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">Open UI</a> is a <a href="https://www.w3.org/community/open-ui/" target="_blank" rel="noreferrer noopener">W3C community group</a> in a quest to allow web developers to style and extend built-in web UI components and controls. Think about current UI elements that you can’t style such as the listbox of a <code>&lt;select&gt;</code> or a color picker, but it’s more than that. Your basic “web UI” where you normally would need JS such as tooltips, notes, panels and general pop-ups such as alerts.</p> <p>Keep in mind that the mission is <strong>not</strong> to replace or update the current web UI, but rather to give us the tools to create our own design systems in a much more convenient way (can’t break the web, but sure as hell can enhance it).</p> <p>To do this, the group works a lot around the parts, states and behaviour of the current UI and a large part of that is accessibility. They approach this by doing research around the large number of design systems out there and carefully mapping out what these systems have in common. On a personal note, I think that this will be the biggest strength, being able to style what couldn’t be styled before and getting the accessibility out of the box. If you’ve never heard of it and you love what you’re hearing, you can <a href="https://open-ui.org/charter/" target="_blank" rel="noreferrer noopener">read the full charter</a>.</p> <h2 id="you-can-start-playing-around-with-it">You can start playing around with it</h2> <p>There is already a Popover API implementation in <a href="https://www.google.com/chrome/canary/" target="_blank" rel="noreferrer noopener">Chrome Canary</a>. All you need to do is go to <code>chrome://flags</code> and enable the <strong>experimental web platform features</strong>. So keep in mind that this article is written with the current state in mind and is subjected to change. I will try to update this as much as possible.</p> <h2 id="what-is-a-popover-pop-up-etc">What is a popover, pop-up, etc…</h2> <p>They are not built-in to the web and we usually use JavaScript to handle them. There are some common characteristics that the Open UI group thinks about. Usually they are <strong>on top of every other content</strong>, they can be “<strong>lightly dismissed</strong>” by clicking beside it or pressing the <kbd>ESC</kbd> key and usually there is only <strong>one “active” at the same time</strong>.</p> <p>Because they are on top of every other element, it makes perfect sense that a popover should be added to the top-layer, which is technically outside of the document flow. This is beneficial as we won’t have to hurt our brains on managing our <code>z-index</code> in CSS (let’s admit, we’ve all been there…)</p> <p>There is a lot of <a href="https://open-ui.org/components/popover.research.explainer/" target="_blank" rel="noreferrer noopener">information on the Popover API on the website</a> and I won’t be copying all of the things written there. Instead, it’s demo time!</p> <h2 id="basic-usage-of-the-popover-api">Basic usage of the Popover API</h2> <p>We can define a Popover by using the <code>popover</code> attribute. By default, the <code>popover</code> will have the “auto” value but we can set it to “manual” as well. Here is the basic syntax:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popover</span><span class="token punctuation">&gt;</span></span> Hi, I&#39;m a popover! <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The popover will automatically be hidden inside of the top-layer.</p> <p><strong>The two different values of popover:</strong></p> <ul> <li><code>popover=”auto”</code> is the default and will create a popover that has a light dismiss. It can be closed by either clicking outside of it, or pressing the <kbd>ESC</kbd> key on the keyboard.</li> <li><code>popover=”manual”</code> will result in a popover that can’t be light dismissed. So a custom action should be provided if needed. If you want the user to perform a specific action within the popup and then close it, this is your value of choice.</li> </ul> <p>One of the biggest strengths of this new API, is that we can choose our own elements and aria-roles for it. In contrast to the dialog element which always gets the dialog role. Yes, this comes with some responsibility, but this also means that we can use it for multiple cases with some careful planning by using the correct elements or roles such as dialog, tooltip, alert, etc…</p> <p>If you want to read up about the differences between the Popover API and the <code>&lt;dialog&gt;</code> element. There is a great article on that by Hidde: <a href="https://hidde.blog/dialog-modal-popover-differences/" target="_blank" rel="noreferrer noopener">Dialogs, modality and popovers seem similar. How are they different?</a></p> <h2 id="showing-hiding-and-toggling-the-popover">Showing, hiding and toggling the popover</h2> <p>What I personally love about this Popover API is that we don’t need any JavaScript to toggle our popovers. To open up our popover, we can simply add the following button to toggle it:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popovertargetaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>toggle<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Open the popover<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popover</span><span class="token punctuation">&gt;</span></span> Hi, I&#39;m a popover! <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>This button introduces two new attributes:</p> <ul> <li><code>popovertarget</code> will reference the id of the popover we wish to show, hide or toggle.</li> <li><code>popovertargetaction</code> provides the action that should occur, options are: “show”, “hide” or “toggle”.</li> </ul> <p>The <code>popovertargetaction</code> is an optional attribute and the default is set to “toggle”.</p> <p>These attributes are only supported on buttons (including: <code>&lt;button&gt;</code>, <code>&lt;input type=”button”&gt;</code>, etc.) as long as the button would not submit a form. In that case the form would be submitted and the popover would not be toggled.</p> <p>If we would like to create a popover where we don’t want a light dismiss, we can easily create this by doing the the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popovertargetaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>show<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Open the popover<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popover</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>manual<span class="token punctuation">&quot;</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">&quot;</span>dialog<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Hi, I&#39;m a popover! <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>some-popover<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popovertargetaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>hide<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Close the popover<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="an-important-note-on-focus-trapping">An important note on focus trapping</h2> <p>Popovers <strong>do not have focus trapping</strong> by default. The reason for that is because they are meant to be used in multiple conditions. Take for example an alert; You might not want to trap focus there but instead keep the focus on the element that triggered the alert. It is however possible to add the “<strong>autofocus</strong>” attribute on (or inside) the popover element which should help with your initial focus. But remember, <strong>this is only changing the next focus and isn’t the same as focus trapping itself</strong>.</p> <p><strong>Take note:</strong> If you’d like to use this for a modal you will need to implement your own focus trapping with JavaScript, and use additional ARIA attributes. Alternatively, one could simply use the <code>&lt;dialog&gt;</code> element with the <code>.showModal()</code> method.</p> <p>Here is a default example of the Popover API on codepen (Remember, use <a href="https://www.google.com/chrome/canary/" target="_blank" rel="noreferrer noopener">Chrome Canary</a> for now).</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/ExpJPga?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="animating-our-popovers">Animating our popovers</h2> <p>The initial demo used to animate before by only using transitions, but during the standardization process this was removed in favor of something else. There are a few open issues at the CSSWG which make a lot more sense for transitioning the popovers. Most of these are part of the <strong>css-transitions-2</strong> spec and include:</p> <ul> <li><a href="https://github.com/w3c/csswg-drafts/issues/4441" target="_blank" rel="noreferrer noopener">Animation of discrete CSS properties</a> (DONE)</li> <li><a href="https://github.com/w3c/csswg-drafts/issues/6429" target="_blank" rel="noreferrer noopener">Animating from/to display: none</a> (RESOLVED)</li> <li><a href="https://github.com/w3c/csswg-drafts/issues/8189" target="_blank" rel="noreferrer noopener">Animating to and from the top layer</a> (DONE)</li> <li> <a href="https://github.com/w3c/csswg-drafts/issues/8174" target="_blank" rel="noreferrer noopener">Initial frame update</a> </li> <li> <a href="https://github.com/w3c/csswg-drafts/issues/8389" target="_blank" rel="noreferrer noopener">Inert for animating to display: none</a> </li> </ul> <p>You can still animate the entry of the popover by using CSS animations, but when it comes to transitions we will have to wait a bit. Here is an animated example 🙂:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/gOddWwq?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Animated popover with Open UI and the popover API"><source src="/_astro/_open-ui-popover.DU0guSgs.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="how-to-anchor-our-top-layered-popovers-to-other-items-in-our-dom---the-anchoring-api">How to anchor our top-layered popovers to other items in our DOM - The anchoring API</h2> <p>It’s difficult to track relative positioning because a popover is outside of the document flow. But there are use cases where we want to “anchor” our popup to another element, especially when you think about tooltips, alerts, etc… So how can we do this?</p> <p>Luckily, there is another new kid on the block, which is the “<strong>Anchoring API</strong>”. It will allow us to tether elements to each other with CSS positioning. I’ll just go over this quickly, but if you want to read up on that, there is a <a href="https://developer.chrome.com/blog/tether-elements-to-each-other-with-css-anchor-positioning/" target="_blank" rel="noreferrer noopener">great intro to the anchoring API by Jhey Tompkins on the Chrome blog</a>.</p> <p>For the final demo in this article, we just need the basics. For anchoring to work, we need to give our element an <code>anchor-name</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.the-anchor</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> --myCoolAnchor<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Now if we want to hang our top-layered element to this anchor, we can use the anchor name and its position:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.my-toplayered-element</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> <span class="token function">anchor</span><span class="token punctuation">(</span>--myCoolAnchor top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span>--myCoolAnchor center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h3 id="handling-multiple-popover-positions-for-tooltips">Handling multiple popover positions for tooltips.</h3> <p>To handle multiple popovers such as tooltips, or in this case toggle tips, we can always add the name inside of a custom property.</p> <p>My tooltip “toggle” will then get an inline style with that custom property and we will use this as an anchor name in CSS.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</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">&quot;</span><span class="token value css language-css"><span class="token property">--anchor-name</span><span class="token punctuation">:</span> --tooltip-1<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></span></span> <span class="token attr-name">popovertarget</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>tooltip<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popovertargetaction</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>toggle<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>click this!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token selector">button</span> <span class="token punctuation">{</span> <span class="token property">anchor-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--anchor-name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Our popover itself will then receive a custom property as well that we can anchor to the toggling element:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>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">&quot;</span>tooltip<span class="token punctuation">&quot;</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">&quot;</span><span class="token value css language-css"><span class="token property">--anchor</span><span class="token punctuation">:</span> --tooltip-1<span class="token punctuation">;</span></span><span class="token punctuation">&quot;</span></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">&quot;</span>note<span class="token punctuation">&quot;</span></span> <span class="token attr-name">popover</span><span class="token punctuation">&gt;</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">&quot;</span>popover-inner<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> ✨Peek-a-boo✨<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>br</span> <span class="token punctuation">/&gt;</span></span>I&#39;m a little popover <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">[popover]</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> <span class="token function">anchor</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--anchor<span class="token punctuation">)</span> top<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> <span class="token function">anchor</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--anchor<span class="token punctuation">)</span> center<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span> <span class="token property">transform-origin</span><span class="token punctuation">:</span> center<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>-50%<span class="token punctuation">,</span> -100%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This way we can create a dynamic system for our “toggletips”, full demo and video:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/jOpRWeo?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Tooltips toggle on click by just using HTML and CSS"><source src="/_astro/_open-ui-tooltip.DxXO8rSZ.mp4" type="video/mp4"> Your browser does not support the video tag.</video> <h3 id="what-about-hovers-for-tooltips">What about hovers for tooltips?</h3> <p>For the moment it seems that hover and focus popups will be picked up in the future, but it’s coming! There are a lot more accessibility caveats to be discussed for those.</p> <h2 id="final-thoughts-on-popovers-and-open-ui">Final thoughts on popovers and Open UI</h2> <p>I think Open UI is one of the best things being worked on at the moment. It might partially be due to frustration of JavaScript libraries not being accessible, slowing my page down or not being customizable enough. I’ve worked with a lot of libraries and hacked a lot of them as well just to get the layout that was needed. This Popover API and things such as <code>&lt;selectmenu&gt;</code> will really make a big impact on the web. I’ve played a lot with <code>&lt;selectmenu&gt;</code> as well, but that’s also something for a later stage (and probably another article in the future).</p> <p>I really enjoyed following the meetings as well, it’s so nice that this is an open group. I’m mostly contributing by listening, learning, spreading the word, creating demos and maybe giving a little opinion or two. This community is really getting attention from the right people. It’s great to follow along with this story and I’m sure many awesome things will be created in the future by the work these people are doing right now. Especially combined with the Anchoring API, the future for styleable UI has never been brighter.</p> <p><strong>Everyone can contribute!</strong> So if you’re interested, it’s time to <a href="https://open-ui.org/get-involved/" target="_blank" rel="noreferrer noopener">get involved with Open UI</a>.</p> <p>I also want to add a little word of thanks to <a href="https://twitter.com/jh3yy" target="_blank" rel="noreferrer noopener">@jh3yy</a>, <a href="https://twitter.com/hdv" target="_blank" rel="noreferrer noopener">@hdv</a> and <a href="https://twitter.com/gregwhitworth" target="_blank" rel="noreferrer noopener">@gregwhitworth</a> for taking the time to help me out and reviewing this article. <strong>🙏</strong></p>Brecht De RuyteFor the love of scroll driven animations in CSShttps://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css/https://utilitybend.com/blog/for-the-love-of-scroll-driven-animations-in-css/Animating elements while a user scrolls the page is a popular practice on the web. With scroll-timeline and the scroll function available in Chrome Canary, we get a nice preview on how these scrolling animations in pure CSS will work. The perfect time to create some demos and play around with it.Tue, 14 Feb 2023 00:00:00 GMT<picture> <source srcset="/_astro/css-heart.CFSCPWsw_cUppF.avif 375w, /_astro/css-heart.CFSCPWsw_S7bV.avif 480w, /_astro/css-heart.CFSCPWsw_ZwKWix.avif 680w, /_astro/css-heart.CFSCPWsw_Z1hvtHg.avif 800w, /_astro/css-heart.CFSCPWsw_Z1dBzI1.avif 980w, /_astro/css-heart.CFSCPWsw_15p6kl.avif 1024w, /_astro/css-heart.CFSCPWsw_ZD07kl.avif 1660w, /_astro/css-heart.CFSCPWsw_Z11LSf0.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/css-heart.CFSCPWsw_nw3C9.webp 375w, /_astro/css-heart.CFSCPWsw_btKop.webp 480w, /_astro/css-heart.CFSCPWsw_Zmaj64.webp 680w, /_astro/css-heart.CFSCPWsw_Z16TPuM.webp 800w, /_astro/css-heart.CFSCPWsw_Z130Vvx.webp 980w, /_astro/css-heart.CFSCPWsw_1nORQ9.webp 1024w, /_astro/css-heart.CFSCPWsw_ZkzkNx.webp 1660w, /_astro/css-heart.CFSCPWsw_Z1P51HX.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/css-heart.CFSCPWsw_kpQf1.png" srcset="/_astro/css-heart.CFSCPWsw_Z2iScND.png 375w, /_astro/css-heart.CFSCPWsw_Z2uUv2n.png 480w, /_astro/css-heart.CFSCPWsw_21Byh5.png 680w, /_astro/css-heart.CFSCPWsw_1gR1Rm.png 800w, /_astro/css-heart.CFSCPWsw_1kKUQB.png 980w, /_astro/css-heart.CFSCPWsw_ZMO9Ei.png 1024w, /_astro/css-heart.CFSCPWsw_Z2wenjY.png 1660w, /_astro/css-heart.CFSCPWsw_1qlHKo.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="An outlined heart" loading="eager" decoding="async" fetchpriority="auto" width="2591" height="1455" class="img-fluid"> </picture> <p>Animating elements while a user scrolls the page is a popular practice on the web. With scroll-timeline and the scroll function available in Chrome Canary, we get a nice preview on how these scrolling animations in pure CSS will work. The perfect time to create some demos and play around with it.</p> <p>If you want to try the demo’s, you should open them in <a href="https://www.google.com/chrome/canary/" target="_blank" rel="noreferrer noopener">Chrome Canary</a> for now, or if you’re reading this and Chrome 115 is released, you might be good by just using that! :)</p> <p>It’s time for some new properties again! Nothing too complicated to start with. Let’s take the following animation into account:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.animate-on-scroll</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> fadeIn ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> fadeIn</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</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 punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>If you want to make this CSS animation run only when a user scrolls the page. Just add the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.animate-on-scroll</span> <span class="token punctuation">{</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>That was easy right? A few things are happening now. The percentage of our keyframes is the same percentage that the user scrolls. So in this case we are fading something from 0 to 1 over the whole length of a page scroll.</p> <p>But what is this <code>scroll()</code> function we see here? This is something new and it’s very exciting.</p> <h2 id="the-scroll-function">The scroll function</h2> <p>The scroll function can have two values inside of it. The first part specifies whether the scroll port we’re targeting is the <code>root</code> scroller or the <code>nearest</code> ancestor. We can also specify the scroll direction in which the animation should occur. Options are: <strong>horizontal</strong>, <strong>vertical</strong> and logical variants <strong>block</strong> and <strong>inline</strong>. The default of this function is the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.animate-on-scroll</span> <span class="token punctuation">{</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>block nearest<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>It’s a really handy feature, especially for targeting the root scroller of a page. Maybe we want to show a little “scroll to top”-button when the user scrolls about 30% of the page’s length. This could be done easily without using JavaScript:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>top<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- some content here --&gt;</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">&quot;</span>#top<span class="token punctuation">&quot;</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">&quot;</span>scroll-to-top<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Scroll to top <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And we can reveal our scroll to top button while scrolling simply by doing the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.scroll-to-top</span> <span class="token punctuation">{</span> <span class="token property">animation</span><span class="token punctuation">:</span> revealScroller ease-out<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> revealScroller</span> <span class="token punctuation">{</span> <span class="token selector">0%, 30%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<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> <span class="token selector">38%, 100%</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">translateY</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>When the user scrolled 30% of the page, the button will reveal itself and will be fully visible by the time the user scrolled 38% of the page. Here is the demo of this:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/xxJBmRV?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style="" loading="lazy"></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Reveal a scroll to top with just CSS"><source src="/_astro/_scroll-to-top-reveal-on-scroll.BQ3iZGfO.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>I had a lot of fun playing around with this. Another useful example that many websites seem to have is a progress bar to indicate how much you’ve read of a certain article. I created my own little version of this with a little “chicky” walking on your screen while scrolling, be warned though, it has a bit of keyframe overload:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/bGjQqXg?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="A chicky walking on the screen on scroll"><source src="/_astro/_chicky-scroll-animation.C9u2D9LD.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <p>The downside is that my animation seems to trigger a bit of moonwalking when scrolling back to top. Let’s just call it a feature for now, ok? 😉</p> <h2 id="naming-a-scroll-timeline-and-scoping-the-parent">Naming a scroll-timeline and scoping the parent</h2> <p>if the animated element doesn’t need to interact with the nearest ancestor or root scroll but rather with a scroll happening somewhere else on our page, we have the ability to target a custom <code>scroll-timeline</code> on the item we want to scroll and add a <code>timeline-scope</code> to the wrapping parent. As a simple example, take a look at the following HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">&gt;</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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Ducks, geese, and waterfowl<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Pheasants, grouse, and allies<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- etc etc etc --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- item we want to animate --&gt;</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">&quot;</span>animation<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>In this example, <code>.list</code> has an <code>overflow-y: auto;</code> and a fixed height. When we scroll in our <code>.list</code> we want <code>.animation</code> to do something. In this case, <code>.animation</code> isn’t a direct child, but we need to start with creating a custom scroll-timeline so we will use a <code>scroll-timeline-name</code> to name our <code>.list</code>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.list</span> <span class="token punctuation">{</span> <span class="token property">scroll-timeline</span><span class="token punctuation">:</span> --listTimeline block<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>scroll-timeline is a shorthand for:</strong></p> <ul> <li><code>scroll-timeline-name</code>: provides a name to the selector</li> <li><code>scroll-timeline-axis</code>: The direction of the scroll: horizontal, vertical, block or inline.</li> </ul> <p>Unfortunately, just adding this won’t be enough because our <code>.animation</code> isn’t a child of the element we’re scrolling, we’ll need some way to let the wrapping parent know that these belong in the same scope, this is where <code>timeline-scope</code> comes in handy. So let’s update the CSS based on our HTML example:</p> <p><strong>Please note</strong> that the timeline-scope will be a bit later to the party as this will be released in <strong>version 116</strong>.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">section</span> <span class="token punctuation">{</span> <span class="token property">timeline-scope</span><span class="token punctuation">:</span> --listTimeline<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.list</span> <span class="token punctuation">{</span> <span class="token comment">/* other scroll snap related code */</span> <span class="token property">scroll-timeline</span><span class="token punctuation">:</span> --listTimeline block<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>By naming this list and scoping the wrapping parent we can now bind our <code>.animation</code> selector by declaring the <code>animation-timeline</code> again, but now using the name instead of the scroll function:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.animation</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> 0<span class="token punctuation">;</span> <span class="token property">right</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> moveBackground alternate linear<span class="token punctuation">;</span> <span class="token property">animation-timeline</span><span class="token punctuation">:</span> --listTimeline<span class="token punctuation">;</span> <span class="token comment">/* some repeating bg image */</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@keyframes</span> moveBackground</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">background-position</span><span class="token punctuation">:</span> 0 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</span> <span class="token punctuation">{</span> <span class="token property">background-position</span><span class="token punctuation">:</span> 0 100px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>These are two results of using these techniques. One for the vertical scroll and another for a horizontal scroll:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/VwBRNwm?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Named timeline scroll example with CSS"><source src="/_astro/_list-scroll-snap-animated-vertical.DmOnwhS7.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/MWBxMKQ?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Horizontal animation with scroll-timeline in CSS"><source src="/_astro/_list-scroll-animation-horizontal.CMvdXaw3.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video> <h2 id="checking-for-support-for-scroll-timeline">Checking for support for scroll-timeline</h2> <p>We can use a feature query to enable our animations only for those browsers that support it. It’s a good idea to do this as I noticed that your animation will just fire right away if the feature isn’t supported.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">animation-timeline</span><span class="token punctuation">:</span> <span class="token function">scroll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* cool scrolly stuff goes here */</span> <span class="token punctuation">}</span> </code></pre> <p>When using this in our projects, it’s nice to check for user preferences if you’re animating a lot of things on scroll, so check if a user doesn’t want a lot of distractions because of the motion. For those people add the following media feature query:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span>prefers-reduced-motion<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* cancel the animations here */</span> <span class="token punctuation">}</span> </code></pre> <p>I didn’t use this in my demo’s, but lately, I always make sure to do this for heavy-animated elements in projects.</p> <h2 id="conclusion-and-final-demo">Conclusion and final demo</h2> <p>There are still a lot of requests and questions being asked on the <a href="https://github.com/w3c/csswg-drafts/issues" target="_blank" rel="noreferrer noopener">CSSWG github</a> and it shows that this is only the beginning. Still, the spec has changed a few times and i’ve updated this article ver since. Personally, I’m wondering if we will be able to target the scroll direction so that my Chicky demo won’t have to moonwalk back into position (So, <a href="https://github.com/w3c/csswg-drafts/issues/8401" target="_blank" rel="noreferrer noopener">I asked the question</a>, can’t hurt to try). Maybe there is an answer in state queries or something else. As it turnes out, it seems that this Spec will really remove the need of some heavy libraries such as <a href="https://greensock.com/gsap/" target="_blank" rel="noreferrer noopener">GSAP</a>. As these CSS animations will run off the main thread (when using transforms), they will be really speedy and running at a whooping 120hz refresh rate. The ability to trigger these little animations and create some extra interaction is a great addition to CSS. But I’m sure many more demo’s will be made soon with people going crazy on these things.</p> <p>For more information on scroll driven animations, you should checkout <a href="https://scroll-driven-animations.style/" target="_blank" rel="noreferrer noopener">scroll-driven-animations.style</a></p> <p>And because I’m creating this as my valentine’s day article, let me share some love for the scroll with this extra demo:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/VwBRgwx?default-tab=result&theme-id=dark" frameborder="0" allowtransparency="true" style=""></iframe></div> <video width="320" height="240" controls="" preload="metadata" aria-label="Scroll animation of a heart with CSS"><source src="/_astro/_animated-heart-on-scroll.q1N7exwM.mp4" type="video/mp4"><p>Your browser does not support the video tag.</p></video>Brecht De RuyteWe've only scratched the surface with :has()https://utilitybend.com/blog/weve-only-scratched-the-surface-with-has/https://utilitybend.com/blog/weve-only-scratched-the-surface-with-has/The :has() pseudo class is really a powerhouse. There are so many cool and amazing demos being released everyday showing how it can solve everyday problems and how it can replace actions we did in JS for years. In this short article: Some extra things I look forward to when playing around with it.Mon, 30 Jan 2023 00:00:00 GMT<picture> <source srcset="/_astro/has-swirly.CT7LP2Pp_1b04h4.avif 375w, /_astro/has-swirly.CT7LP2Pp_XWL3k.avif 480w, /_astro/has-swirly.CT7LP2Pp_qiGxQ.avif 680w, /_astro/has-swirly.CT7LP2Pp_ZjqOPR.avif 800w, /_astro/has-swirly.CT7LP2Pp_ZfwUQC.avif 980w, /_astro/has-swirly.CT7LP2Pp_Azu2V.avif 1024w, /_astro/has-swirly.CT7LP2Pp_1tmvB5.avif 1581w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/has-swirly.CT7LP2Pp_1lAHtx.webp 375w, /_astro/has-swirly.CT7LP2Pp_19ypfN.webp 480w, /_astro/has-swirly.CT7LP2Pp_ATkKk.webp 680w, /_astro/has-swirly.CT7LP2Pp_Z8PbDo.webp 800w, /_astro/has-swirly.CT7LP2Pp_Z4VhE9.webp 980w, /_astro/has-swirly.CT7LP2Pp_T0gyJ.webp 1024w, /_astro/has-swirly.CT7LP2Pp_1LMi7S.webp 1581w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/has-swirly.CT7LP2Pp_ZoQJny.png" srcset="/_astro/has-swirly.CT7LP2Pp_Z1kNxWf.png 375w, /_astro/has-swirly.CT7LP2Pp_Z1wPQaY.png 480w, /_astro/has-swirly.CT7LP2Pp_Z25uUFs.png 680w, /_astro/has-swirly.CT7LP2Pp_2eVFIK.png 800w, /_astro/has-swirly.CT7LP2Pp_2iPzI0.png 980w, /_astro/has-swirly.CT7LP2Pp_Z1hDKVH.png 1024w, /_astro/has-swirly.CT7LP2Pp_ZoQJny.png 1581w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Swirly navigation" loading="eager" decoding="async" fetchpriority="auto" width="1581" height="888" class="img-fluid"> </picture><p>About three months ago, I wrote an article about some <a href="https://utilitybend.com/blog/practical-uses-of-the-has-relational-pseudo-class">practical uses of the :has() relational pseudo class</a>. Because of that article and experimenting with it, I can’t stop thinking about how it can fix some everyday problems. Currently we’re still waiting for some support in Firefox and as I want to keep polyfilling to a minimum in my everyday work… I can’t wait for that to happen.</p> <p>For some reason, :has() is always on my mind, not a week goes by without thinking: “ah, I could’ve done this with CSS”.</p> <p>I’ve been a bit busy at the start of the year with reading, creating demos and presentations, while also releasing an article about <a href="https://www.smashingmagazine.com/2023/01/creating-high-contrast-design-system-css-custom-properties/" target="_blank" rel="noreferrer noopener">creating high-contrast design systems with custom properties on Smashing Magazine</a>, so I will keep things short here.</p> <h2 id="solving-how-we-can-manage-our-content">Solving how we can manage our content</h2> <p>Especially as a person working on a lot of content-heavy websites, I look forward to how we can manage our content a bit better by using <code>:has()</code>. With content mostly provided by a CMS, I find that it could help a lot with structuring content and giving more sense of freedom to clients. As a simple example, It could just be about styling of titles depending on their sibling.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Check if the h2 is followed by a blockquote */</span> <span class="token selector">h2:has(+ blockquote)</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 property">color</span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Only center the h2 if the next sibling is a cta and the cta itself is the last item in our content */</span> <span class="token selector">h2:has(+ .cta:last-child)</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">margin-top</span><span class="token punctuation">:</span> 50px<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> <p>Better ways to structure our content or even better handle some of those crazy combinations that can happen and tackle them with <code>:has()</code> really seems like a much needed feature in CSS.</p> <p>Here is a little (very simple) example of that:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/poZLjLO?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h3 id="structuring-good-ol-tables">Structuring good ol’ tables</h3> <p><strong>Imagine the following use case:</strong> You need a table with two axis of headings. You want to draw a thick vertical line after the headings on the left.</p> <picture> <source srcset="/_astro/table-layout-th-line.Dn3pp1Vk_oHtth.avif 320w, /_astro/table-layout-th-line.Dn3pp1Vk_ZTqQXT.avif 480w, /_astro/table-layout-th-line.Dn3pp1Vk_ZVerez.avif 800w, /_astro/table-layout-th-line.Dn3pp1Vk_1xDRUN.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/table-layout-th-line.Dn3pp1Vk_aQ23d.webp 320w, /_astro/table-layout-th-line.Dn3pp1Vk_Z18ijoX.webp 480w, /_astro/table-layout-th-line.Dn3pp1Vk_Z1a5SED.webp 800w, /_astro/table-layout-th-line.Dn3pp1Vk_1jMquJ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/table-layout-th-line.Dn3pp1Vk_CBFQm.png" srcset="/_astro/table-layout-th-line.Dn3pp1Vk_ZvjHAa.png 320w, /_astro/table-layout-th-line.Dn3pp1Vk_Z1Ot43l.png 480w, /_astro/table-layout-th-line.Dn3pp1Vk_Z1QgDj1.png 800w, /_astro/table-layout-th-line.Dn3pp1Vk_CBFQm.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Table layout with thick border on left column" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="550"> </picture> <p>This is easy, as we can just do the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">th:first-of-type</span> <span class="token punctuation">{</span> <span class="token property">border-right-width</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>But suddenly, somebody decides that the first two items of the <code>tbody</code> should be a table heading. Imagine you have no control of the markup and now we need to find a way to only draw the thick line on the second child of the <code>thead</code> element. Seems like <code>:has()</code> to the rescue again:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">thead:has(+tbody th:nth-child(2)) th:nth-child(2)</span> <span class="token punctuation">{</span> <span class="token property">border-right-width</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">thead:has(+tbody th:nth-child(2)) th:nth-child(1)</span> <span class="token punctuation">{</span> <span class="token property">border-right-width</span><span class="token punctuation">:</span> 1px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>It could also be about placing items sticky inside the table or some other styling you fancy. Here is a simplified demo of this idea:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/VwBBKRm?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="using-has-for-interaction-and-animation">Using :has() for interaction and animation</h2> <p>There are so many possibilities when it comes to animating things by using this pseudo class, as it makes it easier for us to animate two elements in relation to each other. I created a little pagination example with a fancy hover effect:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/OJwpONy?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <p><em>You can ignore the CSS counter for now</em>. I just don’t use it as often as I should and thought I’d give it a go. The important thing here is how we animate out little orbs in relation to the element that was actually hovered.</p> <p>The first thing I did was set a transform and the lightness of a hsl color with a custom property as default.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--move-0</span><span class="token punctuation">:</span> .6<span class="token punctuation">;</span> <span class="token property">--move-1</span><span class="token punctuation">:</span> .4<span class="token punctuation">;</span> <span class="token property">--move-2</span><span class="token punctuation">:</span> .2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li a::before</span> <span class="token punctuation">{</span> <span class="token property">--move</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 18%<span class="token punctuation">;</span> <span class="token comment">/* some other styling here */</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">hsl</span><span class="token punctuation">(</span>330deg 100% <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 property">transform</span><span class="token punctuation">:</span> <span class="token function">translateY</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>--move<span class="token punctuation">)</span> * -65%<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 50px 100px -20px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Then by using <code>:has()</code> I checked the hover (or focus) state of the element and targeted the two items before it and the two items after it. It turns to quite a heavy selector but once you get the hang of it, it’s quite easy.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">li:has(+li a:is(:hover, :focus)) a::before, li:has(a:is(:hover, :focus)) + li a::before</span> <span class="token punctuation">{</span> <span class="token property">--move</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--move-1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 31%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a:is(:hover, :focus)::before</span> <span class="token punctuation">{</span> <span class="token property">--move</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--move-0<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 45%<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>50<span class="token punctuation">,</span> 50<span class="token punctuation">,</span> 93<span class="token punctuation">,</span> 0.25<span class="token punctuation">)</span> 0px 50px 100px -20px<span class="token punctuation">,</span> <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.3<span class="token punctuation">)</span> 0px 30px 60px -30px<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>10<span class="token punctuation">,</span> 37<span class="token punctuation">,</span> 64<span class="token punctuation">,</span> 0.35<span class="token punctuation">)</span> 0px -2px 6px 0px inset<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li:has(+li + li a:is(:hover, :focus)) a::before, li:has(a:is(:hover, :focus)) + li + li a::before</span> <span class="token punctuation">{</span> <span class="token property">--move</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--move-2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--lightness</span><span class="token punctuation">:</span> 21%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>You could read the first part as:</strong></p> <p><em>If the list item has a list item next to which has an a element in it that is hovered or focussed -&gt; style the before of this a element as follows</em></p> <p><em>If the list item had an a-element inside of it with a hover or focus state, style the before pseudo of the next list item’s a-element.</em></p> <p>In the second part you take the items next to the direct siblings and so on. Yep, CSS is starting to sound a lot like a full programming language to me.</p> <h2 id="but-yes-this-is-only-the-surface">But yes… this is only the surface</h2> <p>I really love these demos about <code>:has()</code>, there are a lot of cool ideas being made on how to control elements and the relation they have to each other. For me this really was the biggest thing released in 2022 for CSS and hopefully will have complete browser support soon.</p> <p>I have used it for a bit progressive enhancement - once again, to limit polyfills in projects. If you want to check for browser support, you can do this with the following feature query:</p> <pre class="language-css" data-language="css"><code is:raw="" 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">:has(+*)</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* this has support */</span> <span class="token punctuation">}</span> </code></pre> <p>This checks for a full feature report. If you’d like to know why, I suggest you read the blogpost by Bram.us on <a href="https://www.bram.us/2023/01/04/css-has-feature-detection-with-supportsselector-you-want-has-not-has/" target="_blank" rel="noreferrer noopener">why you want :has(+ <em>), not :has(</em>)</a>.</p> <p>Let’s go for another awesome year of CSS in 2023 and keep making those demo’s ya’ll!</p>Brecht De RuyteCreating A High-Contrast Design System With CSS Custom Propertieshttps://utilitybend.com/blog/creating-a-high-contrast-design-system-with-css-custom-properties/https://utilitybend.com/blog/creating-a-high-contrast-design-system-with-css-custom-properties/Design should never be a trade-off when it comes to creating an accessible web. There are many features coming to the web that will make creating contrast a lot easier. But even though CSS functions such as color-contrast() are only available in Safari behind a flag, we can already do a lot to create contrast in an easy way by using custom properties.Thu, 26 Jan 2023 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2023/01/creating-high-contrast-design-system-css-custom-properties/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteRecap of the year, some thanks and best wishes for 2023https://utilitybend.com/blog/recap-of-the-year-some-thanks-and-best-wishes-for-2023/https://utilitybend.com/blog/recap-of-the-year-some-thanks-and-best-wishes-for-2023/As we’re slowly entering the end of the year, it’s the perfect time for a little recap. I started this blog somewhere near the end of 2021 and continued this journey in writing about the web in 2022. Some important lessons learned, goals achieved and ideas for the future made.Sat, 24 Dec 2022 00:00:00 GMT<picture> <source srcset="/_astro/x-mas-visual.DJvO4SHR_1OY0Oh.avif 375w, /_astro/x-mas-visual.DJvO4SHR_Z3clBF.avif 480w, /_astro/x-mas-visual.DJvO4SHR_GIYTx.avif 680w, /_astro/x-mas-visual.DJvO4SHR_1NbDVh.avif 800w, /_astro/x-mas-visual.DJvO4SHR_1V5llo.avif 980w, /_astro/x-mas-visual.DJvO4SHR_1msq0M.avif 1024w, /_astro/x-mas-visual.DJvO4SHR_1M07R.avif 1660w, /_astro/x-mas-visual.DJvO4SHR_1amVpa.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/x-mas-visual.DJvO4SHR_2cKqbX.webp 375w, /_astro/x-mas-visual.DJvO4SHR_jz3K1.webp 480w, /_astro/x-mas-visual.DJvO4SHR_14vphe.webp 680w, /_astro/x-mas-visual.DJvO4SHR_2aX4iX.webp 800w, /_astro/x-mas-visual.DJvO4SHR_2iQKI5.webp 980w, /_astro/x-mas-visual.DJvO4SHR_Z2j26TF.webp 1024w, /_astro/x-mas-visual.DJvO4SHR_1qtB1l.webp 1660w, /_astro/x-mas-visual.DJvO4SHR_Z12KBgv.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/x-mas-visual.DJvO4SHR_Z2gKLIX.png" srcset="/_astro/x-mas-visual.DJvO4SHR_1QmDJs.png 375w, /_astro/x-mas-visual.DJvO4SHR_Z1NHGu.png 480w, /_astro/x-mas-visual.DJvO4SHR_I7COI.png 680w, /_astro/x-mas-visual.DJvO4SHR_1OzhQs.png 800w, /_astro/x-mas-visual.DJvO4SHR_1WsYgz.png 980w, /_astro/x-mas-visual.DJvO4SHR_24tcyn.png 1024w, /_astro/x-mas-visual.DJvO4SHR_IMLFs.png 1660w, /_astro/x-mas-visual.DJvO4SHR_Z2odBBd.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="X-mas tree" loading="eager" decoding="async" fetchpriority="auto" width="2141" height="1203" class="img-fluid"> </picture><p>To be honest, it wasn’t the first time I had the idea of writing articles on a personal blog, I’ve tried it before but never found the time. In 2022, I decided to just create time for it but without creating too much pressure on releasing articles. Some months were filled with inspiration and the ability to create demos past working hours, while others were a bit slow, but that’s ok. I have a full time job, an amazing wife and a lovely little active (almost) 4 year old daughter. They are and will always be my top priority.</p> <h2 id="looking-back-at-2022-i-really-achieved-some-things-with-this-blog">Looking back at 2022, I really achieved some things with this blog</h2> <p>What a year it’s been… The first time I noticed that this writing paid off was during <a href="https://utilitybend.com/blog/css-day-2022-a-small-recap" target="_blank" rel="noreferrer noopener">CSS day 2022</a> in June. While other years it felt like I was seeing new things the whole time, I noticed that my research paid off and could follow more easily with some of the more in-depth things the speakers were talking about. That’s great because in the end, it actually felt like I learned a lot more. And truth be told, because of all the new goodies in the CSS world, that felt like a good place to start as well. I was also shocked to hear that some people actually read some of my articles which really gave me a boost in motivation. For this, I should probably give a special shout out to <a href="https://www.bram.us/" target="_blank" rel="noreferrer noopener">Bram.us</a> who motivated me to write some guest posts on other blogs.</p> <p>And that’s exactly what I did. I was really scared at first because I’m not that confident in nature. I really make a lot of effort to make my own demos, learn things at my own pace and believe me… I really try some stupid stuff from time to time. Really strange things, such as. Would this work?</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">div:has(p:has(div:has(span))))</span> <span class="token punctuation">{</span> <span class="token comment">/* why would you do that? I dunno, it sounded funny */</span> <span class="token punctuation">}</span> </code></pre> <p>Yes, I could just read the spec for this, but sometimes it’s just more fun to try something and then read why it doesn’t work. It really helps me to get a grasp on things.</p> <p>Just saying, I feel really small compared to some of those awesome writers out there, I’m not at their level at all, but they were very welcoming, supportive and I still learn tons of new stuff from them every day.</p> <p>I also decided to <a href="https://utilitybend.com/blog/coding-on-a-chromebook-part-1" target="_blank" rel="noreferrer noopener">buy a Chromebook</a> to create some small coding tryouts. Thanks to <a href="https://argyleink.com/" target="_blank" rel="noreferrer noopener">Adam Argyle</a> who showed me what you can do with these devices on CSS day. It was a good buy as it really helped me to write articles in my lazy chair and on the go, using this lightweight laptop instead of a heavy Macbook.</p> <h3 id="and-so-i-started-to-do-some-guest-writing">And so I started to do some guest writing</h3> <p>A special thanks to a lot of people such as <a href="https://geoffgraham.me/" target="_blank" rel="noreferrer noopener">Geoff</a>, who really helped me with getting my first articles on CSS-tricks: <a href="https://css-tricks.com/making-a-real-time-clock-with-a-conic-gradient-face/" target="_blank" rel="noreferrer noopener">Making a Real-Time Clock With a Conic Gradient Face</a> and <a href="https://css-tricks.com/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/" target="_blank" rel="noreferrer noopener">Creating Animated, Clickable Cards With the :has() Relational Pseudo Class</a></p> <p>I couldn’t have done this without him and the help I received with writing was eye-opening. I really learned a lot from this experience and hope to continue this in the future. I also wrote my first article for Smashing Magazine: <a href="https://www.smashingmagazine.com/2022/10/fluid-typography-clamp-sass-functions/" target="_blank" rel="noreferrer noopener">Easy Fluid Typography With clamp() Using Sass Functions</a></p> <p>I hope to write more for those platforms in the future. <strong>It’s been great!</strong></p> <p>Some of the comments I received on these articles were really heartwarming and kept me going. It’s not what I expected because… once again. I still lack that confidence.</p> <h2 id="working-at-io">Working at iO</h2> <p>In 2022, I was able to join an expert group within <a href="https://www.iodigital.com/en" target="_blank" rel="noreferrer noopener">iO</a> called the front of front-end. I had the pleasure of learning how people work on other campuses within that group. A special thanks to <a href="https://www.linkedin.com/in/sanderdejong88/" target="_blank" rel="noreferrer noopener">Sander De Jong</a> and <a href="https://www.davebitter.com/" target="_blank" rel="noreferrer noopener">Dave Bitter</a> for starting / leading this group. It made me realize that there are some really smart people within iO of which I can still learn a lot from. Times were a bit hectic especially in the second half of the year with big projects, but even on the job, I was able to further increase some of the skills that I learned at home. I even went on to learn a bit more in-depth React stuff… Can you imagine? A special thanks to all my colleagues there to keep up the mood even though there were a few times with high pressure.</p> <p>Near the end of the year, I was finally able to write my first (exclusive) article for the iO tech_hub, long overdue, but happy to finally do this: <a href="https://techhub.iodigital.com/articles/why-you-should-be-using-new-css-features-today/why-you-should-be-using-new-css-features-today-part-1" target="_blank" rel="noreferrer noopener">Why you should be using new CSS features today</a></p> <p>I was able to give a presentation for “Artevelde Hogeschool” to try and motivate young students to learn CSS. I had some mixed feelings about it because my audience was still just starting out, but nevertheless an interesting experience.</p> <p>I’ll also continue to work inside of the iO party team in Ghent for all <a href="https://www.linkedin.com/feed/update/urn:li:activity:7006966284391178240/?updateEntityUrn=urn%3Ali%3Afs_updateV2%3A%28urn%3Ali%3Aactivity%3A7006966284391178240%2CFEED_DETAIL%2CEMPTY%2CDEFAULT%2Cfalse%29" target="_blank" rel="noreferrer noopener">those geeky events</a> and of course <strong>do some kick-ass front-ending</strong>.</p> <h2 id="not-everything-went-so-smooth">Not everything went so smooth</h2> <p>There were some downs during this year: problems with our house, sickness in the family and really just a lot of bad luck to be honest. I really can’t express my gratitude to Renée (my wife, who I love dearly) in this article to help me overcome some of these struggles. She did a lot for me. I do believe we both need (and deserve) some time off, to take a little breather. That - for one - is a plan we will execute as soon as possible.</p> <h2 id="and-now-a-bit-of-holiday-and-on-to-2023">And now, a bit of holiday and on to 2023</h2> <p>So what does the future hold? I know that I’ll keep writing about accessibility, UX and CSS. There is just so much more to explore. <strong>I still have a lot of things on my list especially related to scroll animations, CSS anchoring API, nesting…</strong> a lot of goodies for next year.</p> <p>I would <strong>love</strong> to return to <a href="https://cssday.nl/" target="_blank" rel="noreferrer noopener">CSS day</a> and maybe give a few presentations myself to get over my lack of confidence a bit. I will try to do some more guest writing but will also keep writing here on my personal blog as I feel it’s getting a bit neglected lately because of all that external writing.</p> <p>I’ve also made a commitment to follow <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">open-ui</a> more closely and to help out as much as possible by creating demo’s, commenting on Github and joining the meetings. Because of all the “JavaScript hacks” I did in the past to create accessible dropdowns and tooltips, this is a feature that really feels important to me.</p> <p>But in the end, I just want to keep making beautiful, accessible things, because in the end, that’s what it’s all about in the front-end world.</p> <blockquote> <p>If there is one thing I want to wish everybody, then it’s a little bit of luck, some smooth sailing through a sea of potential dangers. That little luck can bring a lot of smiles and gets underestimated.</p> </blockquote> <p>So with that. Some thanks were given, a recap was made. I wish you all the best! Have a great holiday and a Happy New Year.</p> <p><strong>Ps</strong>: I made a Christmas tree, it was “quick and dirty” But screw it, I’m on holiday ;) <strong>See you next year!</strong></p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/zYLxEEw?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div>Brecht De RuyteWhy you should be using new CSS features todayhttps://utilitybend.com/blog/why-you-should-be-using-new-css-features-today/https://utilitybend.com/blog/why-you-should-be-using-new-css-features-today/Unless you have no affinity with CSS at all, you should have noticed that new CSS features are skyrocketing like never before. How can we use these new playthings today? I wrote a miniseries about this on the iO tech_hub.Mon, 12 Dec 2022 00:00:00 GMT<picture> <source srcset="/_astro/io.Dg3cHcwk_Z25b8NH.avif 375w, /_astro/io.Dg3cHcwk_tOkKt.avif 480w, /_astro/io.Dg3cHcwk_Z5wM08.avif 680w, /_astro/io.Dg3cHcwk_todc8.avif 800w, /_astro/io.Dg3cHcwk_ZAfldy.avif 980w, /_astro/io.Dg3cHcwk_ZmaGMW.avif 1024w, /_astro/io.Dg3cHcwk_20jHNz.avif 1660w, /_astro/io.Dg3cHcwk_1Wjfzb.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/io.Dg3cHcwk_Z1Ab5it.webp 375w, /_astro/io.Dg3cHcwk_XOogH.webp 480w, /_astro/io.Dg3cHcwk_osgv6.webp 680w, /_astro/io.Dg3cHcwk_XogHm.webp 800w, /_astro/io.Dg3cHcwk_Z6fhHk.webp 980w, /_astro/io.Dg3cHcwk_ZmJlnD.webp 1024w, /_astro/io.Dg3cHcwk_1YK4dS.webp 1660w, /_astro/io.Dg3cHcwk_1EH8qP.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/io.Dg3cHcwk_VsBpc.jpg" srcset="/_astro/io.Dg3cHcwk_16WAjf.jpg 375w, /_astro/io.Dg3cHcwk_Z1oe3Uv.jpg 480w, /_astro/io.Dg3cHcwk_Z1XAbG7.jpg 680w, /_astro/io.Dg3cHcwk_Z1oEbtQ.jpg 800w, /_astro/io.Dg3cHcwk_Z2tiJTx.jpg 980w, /_astro/io.Dg3cHcwk_2bttFL.jpg 1024w, /_astro/io.Dg3cHcwk_ZwdevD.jpg 1660w, /_astro/io.Dg3cHcwk_Zirdpp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="iO tech_hub" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://techhub.iodigital.com/articles/why-you-should-be-using-new-css-features-today/why-you-should-be-using-new-css-features-today-part-1" target="_blank">Read the article at iO tech_hub</a></div>Brecht De RuyteAge 35: slowly becoming an "old developer"https://utilitybend.com/blog/age-35-slowly-becoming-an-old-developer/https://utilitybend.com/blog/age-35-slowly-becoming-an-old-developer/I’ve turned 35 years old and that gives me the excuse of playing the old geezer for a bit…, just for a day… maybe? Not a technical topic this time, just me reminiscing of what I miss of my early days of development, what I’m worried about, and how I see the future. Before I bore you with too many blasts from the past, let’s get those away right now.Mon, 21 Nov 2022 00:00:00 GMT<picture> <source srcset="/_astro/birthday-visual.Wcmr5Pg4_2dm0fY.avif 375w, /_astro/birthday-visual.Wcmr5Pg4_Z2hVbfw.avif 480w, /_astro/birthday-visual.Wcmr5Pg4_2aiaai.avif 680w, /_astro/birthday-visual.Wcmr5Pg4_Z2iL4ut.avif 800w, /_astro/birthday-visual.Wcmr5Pg4_1CFHY3.avif 980w, /_astro/birthday-visual.Wcmr5Pg4_8Qc8r.avif 1024w, /_astro/birthday-visual.Wcmr5Pg4_q4qBC.avif 1473w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/birthday-visual.Wcmr5Pg4_drzj.webp 375w, /_astro/birthday-visual.Wcmr5Pg4_z7oRJ.webp 480w, /_astro/birthday-visual.Wcmr5Pg4_Z2Pnvn.webp 680w, /_astro/birthday-visual.Wcmr5Pg4_yhvCM.webp 800w, /_astro/birthday-visual.Wcmr5Pg4_ZzrOGC.webp 980w, /_astro/birthday-visual.Wcmr5Pg4_Z2pT2of.webp 1024w, /_astro/birthday-visual.Wcmr5Pg4_Z28FMU4.webp 1473w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/birthday-visual.Wcmr5Pg4_1HRjUu.png" srcset="/_astro/birthday-visual.Wcmr5Pg4_Z1lexKo.png 375w, /_astro/birthday-visual.Wcmr5Pg4_ZLkArX.png 480w, /_astro/birthday-visual.Wcmr5Pg4_Z1oinQ5.png 680w, /_astro/birthday-visual.Wcmr5Pg4_ZMatGU.png 800w, /_astro/birthday-visual.Wcmr5Pg4_Z1UTP2k.png 980w, /_astro/birthday-visual.Wcmr5Pg4_1qE5rj.png 1024w, /_astro/birthday-visual.Wcmr5Pg4_1HRjUu.png 1473w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Cake built with CSS with a candle" loading="eager" decoding="async" fetchpriority="auto" width="1473" height="827" class="img-fluid"> </picture><h2 id="the-simplicity-of-the-web">The simplicity of the web</h2> <p>This is one of the things that I miss the most of all. Not saying I don’t love the evolution of the technologies we have, that’s certainly not the case. I’m saying that I sometimes miss that feeling of creating “things on gut feel”. The web has grown up… and frankly, that makes it boring from time to time. Experimenting with code is still the best way to get better, but how many times do we get the chance to do that while working on projects. There are important factors in the world such as accessibility, marketing and UX. But sometimes things might get a bit too perfect, we’re at that point where you actually have to break rules intentionally to invent something new, while in the past, there just weren’t any rules to start with. And I, for one, love breaking rules, but you just get less chances to do so without being virtually murdered by some random guy on Twitter yelling “this is bad practice, you idiot”.</p> <h2 id="there-are-some-lost-skills-that-will-never-return">There are some “lost skills” that will never return</h2> <p>Ah sweet sweet hacks. The box-model hack, the rounded corner hacks, the art of making beautiful nested tables, old sticky footer techniques, clearfixes, the list goes on. Nobody really loves hacks and shady techniques, and thanks to recent developments in CSS they won’t be needed on a day to day basis. It’s great, but also a bit sad because those special things seem to be stuck in my head forever and will never see the light again.</p> <h2 id="i-miss-the-developer-socials-and-personal-blogs">I miss the developer socials and personal blogs</h2> <p>Ah… sweet Twitter from the past where it was mostly a community of tech people showing their stuff and what they had for dinner. It used to be relaxing checking out twitter and just seeing cool stuff on the web, now it’s just full of politics, yep, i’m saying it… politicians ruined Twitter way before Elon Musk.</p> <p>And where did the overly crafty personal blogs go? There was so much good stuff on the web without ads, cookie pop-ups. I say, it’s time to bring blogging back. Here was one of my first attempts at it:</p> <figure class="thirds image"><picture> <source srcset="/_astro/blog2.rWitzgX2_Z1ImX0A.avif 320w, /_astro/blog2.rWitzgX2_ZD8SU.avif 480w, /_astro/blog2.rWitzgX2_2vLxBP.avif 800w, /_astro/blog2.rWitzgX2_210Dfr.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/blog2.rWitzgX2_ZuPiBo.webp 320w, /_astro/blog2.rWitzgX2_1cSvuh.webp 480w, /_astro/blog2.rWitzgX2_Zhd4Cx.webp 800w, /_astro/blog2.rWitzgX2_ZLXXYV.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/blog2.rWitzgX2_desfX.jpg" srcset="/_astro/blog2.rWitzgX2_ZxcFkN.jpg 320w, /_astro/blog2.rWitzgX2_1aw8KR.jpg 480w, /_astro/blog2.rWitzgX2_Z1tzCcf.jpg 800w, /_astro/blog2.rWitzgX2_Z1YlwyD.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="An early blog attempt" loading="lazy" decoding="async" fetchpriority="auto" width="245" height="317"> </picture><figcaption>One of my early attempts to web designing</figcaption></figure> <h2 id="im-mostly-afraid-for-the-next-generation-starting-college-right-now">I’m mostly afraid for the next generation starting college right now.</h2> <p>A lot of the people attending colleges or universities right now had to spend important years in lockdown due to Covid. As if social media addictions weren’t enough, some of them will have a hard time communicating in person. I’m not saying this based on facts but on personal observation. You don’t have to take my word on it… But I really hope this will work out for them. It’s a lot of fun sitting next to a person and fixing some bugs or looking at some awesome stuff they just created and learning how they did it. Tackling problems face to face and having a laugh. I really hope to work with some motivated, fun, born past the year 2000 colleagues in the future. Surely, a lot of people work only remotely, but humans are social beings, that’s a scientific fact and I’m sure many have a bigger need for real-life social interaction than others, even if they don’t know that at the time.</p> <h2 id="but-i-am-hopeful-for-the-future-of-web-development">But I am hopeful for the future of web development</h2> <p>Browsers are really an amazing piece of software. With the latest and upcoming updates to CSS and JavaScript, it’s amazing what we can achieve. Not that long ago, we were forcing designers to respect a 12-column grid, but with all the latest CSS updates there really isn’t a need for this except maybe for time-saving.</p> <p>I’ve been taking a bit more of a deep-dive into React as well in the last couple of months, for me, it’s a necessary evil (sorry, javascripters, you know I love you). I enjoy the things it can do, and although there are just too many libraries out there, it really opens up a lot of opportunities on the web. But when push comes to shove, I’ll always enjoy my niche, which is CSS. It’s really growing up. And the fact that we will less need to rely on JavaScript for layout problems is a good thing for everyone. Lately, I’m also looking a lot into <a href="https://open-ui.org/">open-ui</a>, but that’s a topic for later.</p> <h2 id="so-what-to-do-in-the-next-35-years">So… what to do in the next 35 years?</h2> <p>I really think about this from time to time.. It took me 33 years to start a blog and keep working on it. Even though I had some earlier attempts at blogging, it never really stuck. I’m not certain that working project to project will be something I’ll do until retirement, but for now, I enjoy it. Who knows, maybe I could do a bit more for the future generation of developers? As a self-taught developer, I was always grateful to the people who gave me a chance or believed in me. Maybe it’s time to start giving back a little? Seems like a nice thing to do.</p> <figure class="quarter image"><picture> <source srcset="/_astro/foto.BDsoaObL_Z712P9.avif 320w, /_astro/foto.BDsoaObL_Z11OBTG.avif 480w, /_astro/foto.BDsoaObL_Z1YoNz9.avif 600w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/foto.BDsoaObL_mY0F5.webp 320w, /_astro/foto.BDsoaObL_ZwOyos.webp 480w, /_astro/foto.BDsoaObL_Z1uoK3U.webp 600w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/foto.BDsoaObL_Z1Vi0U4.jpg" srcset="/_astro/foto.BDsoaObL_Z204rw8.jpg 320w, /_astro/foto.BDsoaObL_2aj7dg.jpg 480w, /_astro/foto.BDsoaObL_1cIUxN.jpg 600w" sizes="(max-width: 800px) 100vw, 80vw" alt="An early blog attempt" loading="lazy" decoding="async" fetchpriority="auto" width="300" height="400"> </picture></figure> <p>But for now.. I made a CSS cake!</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/JjZONea?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div>Brecht De RuyteCreating Animated, Clickable Cards With the :has() Relational Pseudo Classhttps://utilitybend.com/blog/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/https://utilitybend.com/blog/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/The CSS :has() pseudo class is rolling out in many browsers with Chrome and Safari already fully supporting it. It's often referred to it as “the parent selector” — as in, we can select style a parent element from a child selector — but there is so much more that :has() can help us solve. One of those things is re-inventing the clickable card pattern many of us love to use from time to time.Thu, 27 Oct 2022 00:00:00 GMT<picture> <source srcset="/_astro/css-tricks.Bj1fKyGA_1cblE4.avif 375w, /_astro/css-tricks.Bj1fKyGA_Z1Gzxjt.avif 480w, /_astro/css-tricks.Bj1fKyGA_2vqntf.avif 680w, /_astro/css-tricks.Bj1fKyGA_Z2qepgo.avif 800w, /_astro/css-tricks.Bj1fKyGA_Z1rk7fq.avif 980w, /_astro/css-tricks.Bj1fKyGA_2rlVfP.avif 1024w, /_astro/css-tricks.Bj1fKyGA_1pE00L.avif 1660w, /_astro/css-tricks.Bj1fKyGA_Z1uLCVL.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/css-tricks.Bj1fKyGA_1uB8aR.webp 375w, /_astro/css-tricks.Bj1fKyGA_Z1o9KMF.webp 480w, /_astro/css-tricks.Bj1fKyGA_Z2gkXNS.webp 680w, /_astro/css-tricks.Bj1fKyGA_Z27NCJA.webp 800w, /_astro/css-tricks.Bj1fKyGA_Z18TkIC.webp 980w, /_astro/css-tricks.Bj1fKyGA_1D3MLR.webp 1024w, /_astro/css-tricks.Bj1fKyGA_BlQwN.webp 1660w, /_astro/css-tricks.Bj1fKyGA_Z180dz5.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/css-tricks.Bj1fKyGA_11M6fd.jpg" srcset="/_astro/css-tricks.Bj1fKyGA_Z7ywcl.jpg 375w, /_astro/css-tricks.Bj1fKyGA_23QHD3.jpg 480w, /_astro/css-tricks.Bj1fKyGA_1bFuBP.jpg 680w, /_astro/css-tricks.Bj1fKyGA_1kcPG8.jpg 800w, /_astro/css-tricks.Bj1fKyGA_2j78H6.jpg 980w, /_astro/css-tricks.Bj1fKyGA_25CIaB.jpg 1024w, /_astro/css-tricks.Bj1fKyGA_13ULUx.jpg 1660w, /_astro/css-tricks.Bj1fKyGA_Z2mPYX1.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="CSS-Tricks" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://css-tricks.com/creating-animated-clickable-cards-with-the-has-relational-pseudo-class/" target="_blank">Read the article at CSS-Tricks</a></div>Brecht De RuytePractical uses of the :has() relational pseudo classhttps://utilitybend.com/blog/practical-uses-of-the-has-relational-pseudo-class/https://utilitybend.com/blog/practical-uses-of-the-has-relational-pseudo-class/The :has() relational pseudo class has landed in Chrome and Safari and even though it gets less hype than for instance container queries, I believe this little pseudo class contains a lot of improvements to the way we write CSS today.Tue, 11 Oct 2022 00:00:00 GMT<picture> <source srcset="/_astro/has-visual-lg.CSuUWO5y_klEtI.avif 375w, /_astro/has-visual-lg.CSuUWO5y_23dbkt.avif 480w, /_astro/has-visual-lg.CSuUWO5y_1UXcbB.avif 680w, /_astro/has-visual-lg.CSuUWO5y_Z2l0TPN.avif 800w, /_astro/has-visual-lg.CSuUWO5y_Z1AeUNE.avif 980w, /_astro/has-visual-lg.CSuUWO5y_Z1q8Vmr.avif 1024w, /_astro/has-visual-lg.CSuUWO5y_P6Abm.avif 1660w, /_astro/has-visual-lg.CSuUWO5y_1Jx12R.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/has-visual-lg.CSuUWO5y_1J3gnc.webp 375w, /_astro/has-visual-lg.CSuUWO5y_Z1ChlzY.webp 480w, /_astro/has-visual-lg.CSuUWO5y_Z1JwkIQ.webp 680w, /_astro/has-visual-lg.CSuUWO5y_ZVjiWk.webp 800w, /_astro/has-visual-lg.CSuUWO5y_ZbxjUb.webp 980w, /_astro/has-visual-lg.CSuUWO5y_1qTDKO.webp 1024w, /_astro/has-visual-lg.CSuUWO5y_Z1n1Wuj.webp 1660w, /_astro/has-visual-lg.CSuUWO5y_ZOddtO.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/has-visual-lg.CSuUWO5y_Z2p8KhC.png" srcset="/_astro/has-visual-lg.CSuUWO5y_12mr2j.png 375w, /_astro/has-visual-lg.CSuUWO5y_Z2jXaUR.png 480w, /_astro/has-visual-lg.CSuUWO5y_Z2rda4J.png 680w, /_astro/has-visual-lg.CSuUWO5y_Z1D08id.png 800w, /_astro/has-visual-lg.CSuUWO5y_ZSe9g4.png 980w, /_astro/has-visual-lg.CSuUWO5y_5rDq7.png 1024w, /_astro/has-visual-lg.CSuUWO5y_2lHaXU.png 1660w, /_astro/has-visual-lg.CSuUWO5y_Z22Qesc.png 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="text that contains :has()" loading="eager" decoding="async" fetchpriority="auto" width="2198" height="1234" class="img-fluid"> </picture><h2 id="using-has-as-a-simple-parent-selector">Using :has() as a simple parent selector</h2> <p>How many times have we added an extra class to check if an element contains an image, or has some extra information that we want to display. For example, You might want to make a grid where the grid item is shown bigger when it contains an image:</p> <picture> <source srcset="/_astro/cards-grid.BHmt18ko_1qpUhP.avif 320w, /_astro/cards-grid.BHmt18ko_RxHfn.avif 480w, /_astro/cards-grid.BHmt18ko_ZSpnrb.avif 800w, /_astro/cards-grid.BHmt18ko_1YsbaC.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/cards-grid.BHmt18ko_1IPGND.webp 320w, /_astro/cards-grid.BHmt18ko_1aXtLb.webp 480w, /_astro/cards-grid.BHmt18ko_ZzYAUn.webp 800w, /_astro/cards-grid.BHmt18ko_2hRWGq.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/cards-grid.BHmt18ko_Zk7Ix8.png" srcset="/_astro/cards-grid.BHmt18ko_ZrNkGN.png 320w, /_astro/cards-grid.BHmt18ko_Z10FxJg.png 480w, /_astro/cards-grid.BHmt18ko_2ixun7.png 800w, /_astro/cards-grid.BHmt18ko_6dUaY.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A grid with cards, first item spanned" loading="lazy" decoding="async" fetchpriority="auto" width="710" height="490"> </picture> <p>Whichever the templating language you’re using. You’ll probably add a condition to check if an image is available like this:</p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"><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">&quot;</span>articles<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">for</span> article <span class="token operator">in</span> articles <span class="token delimiter punctuation">%}</span></span> <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">&quot;</span>article<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">if</span> article<span class="token punctuation">.</span>image <span class="token delimiter punctuation">%}</span></span> has-image<span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">endif</span> <span class="token delimiter punctuation">%}</span></span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token twig language-twig"><span class="token comment">{# content here #}</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> {% endfor %) <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>While this works and has worked for many years, there is a fundamental problem with this approach. We are actually using different class names and controlling those with our templating language. Usually adding a class name such as <code>.has-image</code> , but this can lead to inconsistencies when working with bigger teams. For example, another person might prefer to add a class to the grid saying <code>.is-wide</code> based on the availability of the image. Before you know it, those inconsistencies pile up, and the project becomes harder to maintain. With the <a href="https://www.w3.org/TR/selectors-4/#has-pseudo" target="_blank" rel="noreferrer noopener">:has() pseudo class</a>, all of this is in the past as we can check the availability of an image inside of our grid with ease:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">article:has(figure)</span> <span class="token punctuation">{</span> <span class="token comment">/* do stuff */</span> <span class="token punctuation">}</span> </code></pre> <p>Here is a full example of that in action:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/vYjePzw?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="using-has-and-not-pseudo-classes-combined">Using :has() and :not() pseudo classes combined</h2> <picture> <source srcset="/_astro/gandalf-you-shall-not-pass.D_qYbVqC_Zz6lnr.avif 320w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_Z1I4fNG.avif 480w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_2u2wCd.avif 800w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_22wtDE.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/gandalf-you-shall-not-pass.D_qYbVqC_24LiVi.webp 320w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_UNov3.webp 480w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_3I381.webp 800w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_ZnLYPx.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/gandalf-you-shall-not-pass.D_qYbVqC_ZOikI7.jpg" srcset="/_astro/gandalf-you-shall-not-pass.D_qYbVqC_Z1XPKRz.jpg 320w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_1Wnsv7.jpg 480w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_15i785.jpg 800w, /_astro/gandalf-you-shall-not-pass.D_qYbVqC_CM49w.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Gandalf in Lord of the rings with text: you shall not past" loading="lazy" decoding="async" fetchpriority="auto" width="1600" height="900"> </picture> <p>Taking the previous grid a step further we might want to display our grid a bit differently when there aren’t any images (figures) available. We can easily do this by combining the :has() and :not() selector. In this use-case I just wanted the “only text grid” to fill the space by using a flex layout instead of grid. So adding the following does just that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.articles:not(:has(figure))</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">text-align</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.articles:not(:has(figure)) article</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 40px 20px<span class="token punctuation">;</span> <span class="token property">flex</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">min-width</span><span class="token punctuation">:</span> 280px<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Be mindful that the order plays an important role here. For example:</p> <ul> <li><code>.articles:not(:has(figure))</code> Will target <code>.articles</code> that doesn’t have a figure</li> <li><code>.articles:has(:not(figure))</code> Will target <code>.articles</code> that has anything in it besides a figure. So an empty <code>.articles</code> would not be targeted in this case</li> </ul> <p>And you can see this in action here:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/qBYMQpL?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="changing-a-list-type-based-on-hover-without-hassle">Changing a list-type based on hover without hassle</h2> <p>It might be something that isn’t as popular as it used to be when it comes to design, but I really implemented a lot of hover states in footer links where the bullet of a list item had to be filled when hovering over it. This usually required adding a pseudo element with absolute positioning. An image says more than a thousand words so let me just show what I’m talking about:</p> <picture> <source srcset="/_astro/has-list.Bhlg952M_1gp1Rh.avif 320w, /_astro/has-list.Bhlg952M_ZaBWY3.avif 480w, /_astro/has-list.Bhlg952M_Z1e1haV.avif 800w, /_astro/has-list.Bhlg952M_ZK6tBg.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/has-list.Bhlg952M_NY2Xt.webp 320w, /_astro/has-list.Bhlg952M_ZC2VRQ.webp 480w, /_astro/has-list.Bhlg952M_Z1Frg4J.webp 800w, /_astro/has-list.Bhlg952M_Z1cwsv4.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/has-list.Bhlg952M_Z2oXxkF.png" srcset="/_astro/has-list.Bhlg952M_Zns1Q8.png 320w, /_astro/has-list.Bhlg952M_Z1Ou1Hs.png 480w, /_astro/has-list.Bhlg952M_2ciMTA.png 800w, /_astro/has-list.Bhlg952M_Z2oXxkF.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A list with a hover state that highlights the bullet" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="550"> </picture> <p>This could be done by creating a simple unordered list and doing something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>_blank<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Tim Berners-Lee<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>_blank<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Robert Cailliau<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>_blank<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Vint Cerf<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>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">&quot;</span>...<span class="token punctuation">&quot;</span></span> <span class="token attr-name">target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>_blank<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Bob Kahn<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><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 punctuation">}</span> <span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token property">margin-block</span><span class="token punctuation">:</span> 6px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a</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">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #6247AA<span class="token punctuation">;</span> <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<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">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a::before</span> <span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</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> 10px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">margin-inline-end</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid #6247AA<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">a:hover, a:focus</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> #A06CD5<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">a:hover::before, a:focus::before</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> #A06CD5<span class="token punctuation">;</span> <span class="token property">border-color</span><span class="token punctuation">:</span> #A06CD5<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>But now, let’s enter the :has() pseudo element into the equation and use our default list styles to create pretty much the same thing with less lines of code:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">ul</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> circle<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 punctuation">}</span> <span class="token selector">li</span> <span class="token punctuation">{</span> <span class="token property">margin-block</span><span class="token punctuation">:</span> 6px<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #6247AA<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">font-size</span><span class="token punctuation">:</span> 27px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">li:has(:hover, :focus-within)</span> <span class="token punctuation">{</span> <span class="token property">list-style</span><span class="token punctuation">:</span> disc<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #A06CD5<span class="token punctuation">;</span> <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> currentColor<span class="token punctuation">;</span> <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<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">text-decoration</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>There is a minor drawback here and that is that we won’t be able to add a <a href="https://www.w3schools.com/css/css3_transitions.asp" target="_blank" rel="noreferrer noopener">transition</a> based on the list-style. But it’s still a pretty neat thing to do. I just love the fact that this is possible. If you have followed and read some of my articles before then you know that sometimes I like to make things that aren’t completely practical but just fun to do. (For example: <a href="https://codepen.io/utilitybend/full/jOLmgdQ)" target="_blank" rel="noreferrer noopener">My little container query flappy bird</a>. Below you can see the full example of the lists on hover.</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/NWMagxP?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="form-validation-messaging-with-has-to-check-invalid-states">Form validation messaging with :has() to check invalid states</h2> <p>Forms have never been my favorite thing to style but lately they have become less tedious because of all the possibilities out there (<a href="https://codepen.io/utilitybend/full/NWyeXra" target="_blank" rel="noreferrer noopener">accent-color</a>, appearance, …). Also, <a href="https://open-ui.org/" target="_blank" rel="noreferrer noopener">open-ui</a> is getting a lot more love from the community and that’s a good thing. I’m really looking forward to getting more control over forms eventhough I do understand the accessibility and UX concerns.</p> <p>But let’s get into the validation of forms. Most of the time we had to rely on JavaScript to give us instant feedback when a user is typing something that isn’t allowed. In the previous two examples I’ve shown we can check for elements, states such as hovering of a link which means we can now check for states of our form elements. Let’s start off with the demo this time:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/xxjXXQr?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <p>We created an HTML5 input field with a regex validation that only accepts lowercase text. By using the following we can check if the form is invalid:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">fieldset:has(:invalid) .feedback</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 punctuation">}</span> </code></pre> <p>There is something else we should take care of. Because we added the required flag to the input field, an empty field will also be considered invalid and show the error message. We can fix that by checking for placeholder text:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">fieldset:has(:placeholder-shown) .feedback</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>Note:</strong> it might be a bit “hacky”. But you could use an empty placeholder with a space in it if you just don’t want placeholder text inside of the element or hide it with CSS.</p> <h2 id="creating-a-tiger-striped-table-based-on-the-number-of-rows">Creating a tiger striped table based on the number of rows</h2> <p>Another awesome thing we can do is to check the amount of children our parent selector has by combining <code>:nth-child</code> with the <code>:has()</code> pseudo class. For example, I created a table that only shows a tiger striped pattern when it has more than 5 children (or rows…). I love the fact that we can keep this layout decision in CSS, rather than relying on our templating files to add an extra class. We can achieve this by this little bit of code, it’s that easy:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">table:has(tr:nth-child(5)) tr:nth-child(odd)</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> hotpink<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> </code></pre> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/PoexLPx?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="weve-only-scratched-the-surface">We’ve only scratched the surface</h2> <p>There are already many examples out there on how to use this new pseudo class and I’m sure many more will follow. This really packs a lot of power and can change the way we handle front-end development in the future.</p> <blockquote> <p>It :has() been a blast experimenting with this relational pseudo class and I’ll sure be playing around with it a lot more.</p> </blockquote>Brecht De RuyteEasy Fluid Typography With clamp() Using Sass Functionshttps://utilitybend.com/blog/easy-fluid-typography-with-clamp-using-sass-functions/https://utilitybend.com/blog/easy-fluid-typography-with-clamp-using-sass-functions/Fluid typography is getting a lot more popular, especially since the clamp() math function is available in every evergreen browser. But if we’re honest, it’s still a lot of mathematics to achieve this. You can use tools such as utopia.fyi, which are fantastic. But in large projects, it can get messy pretty fast. So let's make this easy with Sass.Wed, 05 Oct 2022 00:00:00 GMT<picture> <source srcset="/_astro/smashing-magazine.4NoIcH7S_Z4Yi1n.avif 375w, /_astro/smashing-magazine.4NoIcH7S_Z1DhuXK.avif 480w, /_astro/smashing-magazine.4NoIcH7S_Z1naQHc.avif 680w, /_astro/smashing-magazine.4NoIcH7S_Jnrc5.avif 800w, /_astro/smashing-magazine.4NoIcH7S_1xE7mo.avif 980w, /_astro/smashing-magazine.4NoIcH7S_ZYTsJA.avif 1024w, /_astro/smashing-magazine.4NoIcH7S_Z2qo9Vi.avif 1660w, /_astro/smashing-magazine.4NoIcH7S_2i6Eq5.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/smashing-magazine.4NoIcH7S_1KjWP5.webp 375w, /_astro/smashing-magazine.4NoIcH7S_c1JRH.webp 480w, /_astro/smashing-magazine.4NoIcH7S_s8o9g.webp 680w, /_astro/smashing-magazine.4NoIcH7S_Z2uuqKo.webp 800w, /_astro/smashing-magazine.4NoIcH7S_Z1GdKA5.webp 980w, /_astro/smashing-magazine.4NoIcH7S_8FYTm.webp 1024w, /_astro/smashing-magazine.4NoIcH7S_Z1hMGhl.webp 1660w, /_astro/smashing-magazine.4NoIcH7S_24fd01.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/smashing-magazine.4NoIcH7S_13bXLc.jpg" srcset="/_astro/smashing-magazine.4NoIcH7S_ZFI1B.jpg 375w, /_astro/smashing-magazine.4NoIcH7S_Z1yXUXY.jpg 480w, /_astro/smashing-magazine.4NoIcH7S_Z1iRhHq.jpg 680w, /_astro/smashing-magazine.4NoIcH7S_NG1bQ.jpg 800w, /_astro/smashing-magazine.4NoIcH7S_1BWGma.jpg 980w, /_astro/smashing-magazine.4NoIcH7S_1bwj8i.jpg 1024w, /_astro/smashing-magazine.4NoIcH7S_ZeWn3p.jpg 1660w, /_astro/smashing-magazine.4NoIcH7S_Z1541Dy.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Smashing Magazine" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://www.smashingmagazine.com/2022/10/fluid-typography-clamp-sass-functions/" target="_blank">Read the article at Smashing Magazine</a></div>Brecht De RuyteMaking a Real-Time Clock With a Conic Gradient Facehttps://utilitybend.com/blog/making-a-real-time-clock-with-a-conic-gradient-face-css-tricks/https://utilitybend.com/blog/making-a-real-time-clock-with-a-conic-gradient-face-css-tricks/My first article for CSS-tricks on how to create a watch face with a conic gradient Using custom-properties and a little bit of Javascript.Mon, 19 Sep 2022 00:00:00 GMT<picture> <source srcset="/_astro/css-tricks.Bj1fKyGA_1cblE4.avif 375w, /_astro/css-tricks.Bj1fKyGA_Z1Gzxjt.avif 480w, /_astro/css-tricks.Bj1fKyGA_2vqntf.avif 680w, /_astro/css-tricks.Bj1fKyGA_Z2qepgo.avif 800w, /_astro/css-tricks.Bj1fKyGA_Z1rk7fq.avif 980w, /_astro/css-tricks.Bj1fKyGA_2rlVfP.avif 1024w, /_astro/css-tricks.Bj1fKyGA_1pE00L.avif 1660w, /_astro/css-tricks.Bj1fKyGA_Z1uLCVL.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/css-tricks.Bj1fKyGA_1uB8aR.webp 375w, /_astro/css-tricks.Bj1fKyGA_Z1o9KMF.webp 480w, /_astro/css-tricks.Bj1fKyGA_Z2gkXNS.webp 680w, /_astro/css-tricks.Bj1fKyGA_Z27NCJA.webp 800w, /_astro/css-tricks.Bj1fKyGA_Z18TkIC.webp 980w, /_astro/css-tricks.Bj1fKyGA_1D3MLR.webp 1024w, /_astro/css-tricks.Bj1fKyGA_BlQwN.webp 1660w, /_astro/css-tricks.Bj1fKyGA_Z180dz5.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/css-tricks.Bj1fKyGA_11M6fd.jpg" srcset="/_astro/css-tricks.Bj1fKyGA_Z7ywcl.jpg 375w, /_astro/css-tricks.Bj1fKyGA_23QHD3.jpg 480w, /_astro/css-tricks.Bj1fKyGA_1bFuBP.jpg 680w, /_astro/css-tricks.Bj1fKyGA_1kcPG8.jpg 800w, /_astro/css-tricks.Bj1fKyGA_2j78H6.jpg 980w, /_astro/css-tricks.Bj1fKyGA_25CIaB.jpg 1024w, /_astro/css-tricks.Bj1fKyGA_13ULUx.jpg 1660w, /_astro/css-tricks.Bj1fKyGA_Z2mPYX1.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="CSS-Tricks" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><div><a href="https://css-tricks.com/making-a-real-time-clock-with-a-conic-gradient-face/" target="_blank">Read the article at CSS-Tricks</a></div>Brecht De RuyteCoding on a Chromebook - part 2https://utilitybend.com/blog/coding-on-a-chromebook-part-2/https://utilitybend.com/blog/coding-on-a-chromebook-part-2/I purchased a Chromebook for my little coding projects. In the first part I wrote about the basic development setup. In this post, we're taking a bit of a deeper dive: installing git, node, filezilla, some browsers, Docker and updating the terminal on ChromeOS. So let's open the terminal and dive right into it.Sun, 07 Aug 2022 00:00:00 GMT<picture> <source srcset="/_astro/dino2.DE7vba3e_Z1vNM46.avif 375w, /_astro/dino2.DE7vba3e_WpiOA.avif 480w, /_astro/dino2.DE7vba3e_Z2sx3hT.avif 680w, /_astro/dino2.DE7vba3e_Z1M5430.avif 800w, /_astro/dino2.DE7vba3e_Z2kIBPs.avif 980w, /_astro/dino2.DE7vba3e_Z21gCFt.avif 1024w, /_astro/dino2.DE7vba3e_ZHfBgc.avif 1660w, /_astro/dino2.DE7vba3e_ZdrCtv.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/dino2.DE7vba3e_Zih7ET.webp 375w, /_astro/dino2.DE7vba3e_2aVXdM.webp 480w, /_astro/dino2.DE7vba3e_Z1f0nSH.webp 680w, /_astro/dino2.DE7vba3e_ZyxoDN.webp 800w, /_astro/dino2.DE7vba3e_Z17bWrg.webp 980w, /_astro/dino2.DE7vba3e_fURS5.webp 1024w, /_astro/dino2.DE7vba3e_1yVTim.webp 1660w, /_astro/dino2.DE7vba3e_ZERBnj.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/dino2.DE7vba3e_ZCqAr7.jpg" srcset="/_astro/dino2.DE7vba3e_ZkDuoj.jpg 375w, /_astro/dino2.DE7vba3e_28zAun.jpg 480w, /_astro/dino2.DE7vba3e_Z1hmKC7.jpg 680w, /_astro/dino2.DE7vba3e_ZATLnd.jpg 800w, /_astro/dino2.DE7vba3e_Z19ykaF.jpg 980w, /_astro/dino2.DE7vba3e_ZVqEFC.jpg 1024w, /_astro/dino2.DE7vba3e_mzlIE.jpg 1660w, /_astro/dino2.DE7vba3e_Z2lVEhC.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="2 Dinos with heart in html" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><picture> <source srcset="/_astro/chromebook-terminal.CxfqGI1n_1vBGcT.avif 320w, /_astro/chromebook-terminal.CxfqGI1n_cskJI.avif 480w, /_astro/chromebook-terminal.CxfqGI1n_aEKu3.avif 800w, /_astro/chromebook-terminal.CxfqGI1n_Z2pD39v.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-terminal.CxfqGI1n_1hKeLP.webp 320w, /_astro/chromebook-terminal.CxfqGI1n_Z1o6Fl.webp 480w, /_astro/chromebook-terminal.CxfqGI1n_Z3bFV1.webp 800w, /_astro/chromebook-terminal.CxfqGI1n_2qGDem.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-terminal.CxfqGI1n_1JvSzY.png" srcset="/_astro/chromebook-terminal.CxfqGI1n_Azu8s.png 320w, /_astro/chromebook-terminal.CxfqGI1n_ZHyQjI.png 480w, /_astro/chromebook-terminal.CxfqGI1n_ZJmqzo.png 800w, /_astro/chromebook-terminal.CxfqGI1n_1JvSzY.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Terminal in chrome os" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="550"> </picture> <h2 id="installing-git-on-chromeos">Installing Git on ChromeOS</h2> <p>Well, we’re actually in a linux environment here, so all you have to do is add the following command in your terminal, it’s that easy:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> <span class="token function">ssh</span> <span class="token function">git</span> </code></pre> <h2 id="installing-node---nvm">Installing Node - NVM</h2> <p>Personally, I like to use a <a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noreferrer noopener">node version manager</a> for all my node related things. It allows you to run multiple versions of node. I have some old legacy projects that I will work on occasionally, it just makes things easier.</p> <p>Node required gnup2, so let’s install this first:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> gnupg2 </code></pre> <p>To install NVM, add the following command:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">curl</span> -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh <span class="token operator">|</span> <span class="token function">bash</span> </code></pre> <p>If your chromebook doesn’t have curl installed, you might need to run the following command first:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> <span class="token function">curl</span> </code></pre> <p>When this is installed you can change node versions and install them. Use the following command to see your current version:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">node</span> <span class="token parameter variable">-v</span> </code></pre> <p>You can install another version by running the command:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash">nvm <span class="token function">install</span> <span class="token punctuation">[</span>node version here<span class="token punctuation">]</span> </code></pre> <p>To see the full list of commands available, just type:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash">nvm <span class="token parameter variable">--help</span> </code></pre> <h2 id="installing-filezilla-some-browsers-and-alternative-design-tools">Installing Filezilla, some browsers and alternative design tools</h2> <h3 id="filezilla">Filezilla</h3> <p>ChromeOS recently has gotten some features to make ssh connection a lot easier which I still need to take a look into. However, for most of my smaller projects (which are just some simple coding demo’s or presentations), I just need a bit of FTPing to get the job done.</p> <p>For this, I like to use Filezilla, just go to your terminal and type:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> filezilla </code></pre> <h3 id="browsers">Browsers</h3> <p>You might want to test some things on different browsers. One thing that I miss on ChromeOS is a separate Chrome Canary desktop browser and Firefox nightly, but running the ChromeOS in beta mode does the trick for now as it allows me to check flags for container queries and all other goodies.</p> <p>You can install some dev versions of browsers on the Linux platform, so here is how to install Firefox and Chromium browser:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> firefox-esr <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> chromium </code></pre> <p>Alternatively you can access the google play store and add some mobile browsers from there. It’s great to have mobile and desktop browsers on the same device to do some testing!</p> <h3 id="alternative-design-tools">Alternative design tools</h3> <p>There are some alternatives on Linux for your adobe software. They are not quite the same but to do some simple photo editing and making some vectors they seem to work fine. I would recommend Figma for your “real design work” though.</p> <ul> <li><a href="https://www.gimp.org/" target="_blank" rel="noreferrer noopener">Gimp</a> as an alternative to photoshop to edit images</li> <li><a href="https://inkscape.org/" target="_blank" rel="noreferrer noopener">Inkscape</a> as an alternative to illustrator for vectors</li> <li><a href="https://imagemagick.org" target="_blank" rel="noreferrer noopener">ImageMagick</a> to do some in-terminal editing</li> </ul> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> gimp <span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> inkscape <span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> imagemagick </code></pre> <picture> <source srcset="/_astro/gimp.BEiOUifU_Z248JX0.avif 320w, /_astro/gimp.BEiOUifU_Z1MLXyK.avif 480w, /_astro/gimp.BEiOUifU_6Qfdz.avif 800w, /_astro/gimp.BEiOUifU_Z2aHPwt.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/gimp.BEiOUifU_Z2lJR6l.webp 320w, /_astro/gimp.BEiOUifU_Z25o5H6.webp 480w, /_astro/gimp.BEiOUifU_ZaJQTL.webp 800w, /_astro/gimp.BEiOUifU_Z2sjWEO.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/gimp.BEiOUifU_GdWw5.png" srcset="/_astro/gimp.BEiOUifU_MN35y.png 320w, /_astro/gimp.BEiOUifU_149OtN.png 480w, /_astro/gimp.BEiOUifU_Z26o5wN.png 800w, /_astro/gimp.BEiOUifU_GdWw5.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Terminal in chrome os" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="550"> </picture> <h2 id="installing-docker">Installing Docker</h2> <p>It should be possible to run apache directly on Chrome. I am however not the best PHP developer and mostly rely on Docker for all things PHP. This makes a lot of these tasks a lot easier for me.</p> <p>Let’s check if everything is up to date, this is just good practice:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> update </code></pre> <p>Next up add the following command to install the docker dependencies:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> <span class="token punctuation">\</span> ca-certificates <span class="token punctuation">\</span> <span class="token function">curl</span> <span class="token punctuation">\</span> gnupg <span class="token punctuation">\</span> lsb-release </code></pre> <p>Then let’s add the Docker’s official GPG key:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">mkdir</span> <span class="token parameter variable">-p</span> /etc/apt/keyrings </code></pre> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-fsSL</span> https://download.docker.com/linux/ubuntu/gpg <span class="token operator">|</span> <span class="token function">sudo</span> gpg <span class="token parameter variable">--dearmor</span> <span class="token parameter variable">-o</span> /etc/apt/keyrings/docker.gpg </code></pre> <p>Use the following command to set up the repository:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"> <span class="token builtin class-name">echo</span> <span class="token punctuation">\</span> <span class="token string">&quot;deb [arch=<span class="token variable"><span class="token variable">$(</span>dpkg --print-architecture<span class="token variable">)</span></span> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ <span class="token variable"><span class="token variable">$(</span>lsb_release <span class="token parameter variable">-cs</span><span class="token variable">)</span></span> stable&quot;</span> <span class="token operator">|</span> <span class="token function">sudo</span> <span class="token function">tee</span> /etc/apt/sources.list.d/docker.list <span class="token operator">&gt;</span> /dev/null </code></pre> <p>Then we want to update the apt package index again by running:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> update </code></pre> <p>And finally install our docker engine:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> docker-ce docker-ce-cli containerd.io docker-compose-plugin </code></pre> <p>Let’s verify if these things worked by running the hello-world image:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">docker</span> run hello-world </code></pre> <picture> <source srcset="/_astro/chromebook-oh-my-zsh.CB5-W_JO_Z1AjY0f.avif 320w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_ZaldLE.avif 480w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_Z22Ob8r.avif 800w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_2bpVDB.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-oh-my-zsh.CB5-W_JO_Z1Obqqj.webp 320w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_ZocFcI.webp 480w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_Z2gFCyv.webp 800w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_1Wyudx.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-oh-my-zsh.CB5-W_JO_1gnJza.png" srcset="/_astro/chromebook-oh-my-zsh.CB5-W_JO_Z2vmb4G.png 320w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_Z15npQ6.png 480w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_27kKB3.png 800w, /_astro/chromebook-oh-my-zsh.CB5-W_JO_1gnJza.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="docker success message" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="624"> </picture> <p><strong>A note on Docker</strong>: Docker is quite heavy on ChromeOS and isn’t super fast in booting. However, it seemed to do everything I needed with ease. I just handle a few websites with Docker, but nothing too harsh on the machine. If you plan to do some heavy lifting with it, I would recommend buying a high-end Chromebook.</p> <p>You can also install the <a href="https://docs.docker.com/desktop/install/linux-install/" target="_blank" rel="noreferrer noopener">Docker desktop app for Linux here</a>. But it didn’t want to start on my machine. Working purely inside the terminal did work fine.</p> <h2 id="oh-my-zsh">Oh my zsh</h2> <picture> <source srcset="/_astro/chromebook-oh-my-zsh-2.CIHsBK1o_uCcIg.avif 320w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_Z12tOsC.avif 480w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_ZJbLRN.avif 800w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_1JE0Dl.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-oh-my-zsh-2.CIHsBK1o_NSkhq.webp 320w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_ZIdGTs.webp 480w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_ZpUEjD.webp 800w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_23U8cv.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-oh-my-zsh-2.CIHsBK1o_Z1iwaIm.png" srcset="/_astro/chromebook-oh-my-zsh-2.CIHsBK1o_2wDaau.png 320w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_Yw7XB.png 480w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_1hOayq.png 800w, /_astro/chromebook-oh-my-zsh-2.CIHsBK1o_Z1iwaIm.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Oh My ZSH with agnoster theme" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="624"> </picture> <p>To set up zsh, we need to set a sudo password. By default Linux on Chrome OS doesn’t come with a password for sudo. This is however required to set zsh as the default shell.</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">su</span> </code></pre> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">passwd</span> <span class="token punctuation">[</span>your user name<span class="token punctuation">]</span> </code></pre> <p>You will then be prompted to set your password. Enter it, and once again to confirm.</p> <h3 id="add-the-following-commands-to-install-zsh-and-oh-my-zsh">Add the following commands to install zsh and Oh my zsh</h3> <p>Insert the following lines in your terminal, one by one to install Oh my zsh</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> <span class="token function">zsh</span> chsh <span class="token parameter variable">-s</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">which</span> <span class="token function">zsh</span><span class="token variable">)</span></span> <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> <span class="token function">wget</span> <span class="token function">git</span> <span class="token function">wget</span> https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh <span class="token parameter variable">-O</span> - <span class="token operator">|</span> <span class="token function">zsh</span> <span class="token function">cp</span> ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc <span class="token builtin class-name">source</span> ~/.zshrc </code></pre> <p>And now we will edit our config file by adding the following command:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">vi</span> ~/.zshrc </code></pre> <p>Personally, i don’t like editing files that way, so if you have VScode installed, you can use the following command:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash">code ~/.zshrc </code></pre> <p>One of my favorite themes is agnoster, I highly recommend it. On line 10 add the following:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token assign-left variable">ZSH_THEME</span><span class="token operator">=</span><span class="token string">&quot;agnoster&quot;</span> </code></pre> <p>Now save and exit the file and add the following command again:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token builtin class-name">source</span> ~/.zshrc </code></pre> <p>Next, let’s update the fonts. Open up your default chrome browser and type <code>ctrl+alt+t</code>. This will open up a new tab with crosh, let’s change the default theme settings with <code>ctrl+alt+p</code></p> <p>You will get a window with theme settings.</p> <ol> <li>Enter “<a href="https://cdn.jsdelivr.net/gh/wernight/powerline-web-fonts@ba4426cb0c0b05eb6cb342c7719776a41e1f2114/PowerlineFonts.css" target="_blank" rel="noreferrer noopener">this url</a>” in front of Custom CSS (URI)</li> <li>Enter “Hack” before “DejaVu Sans Mono” in front of Text Font Family like: “Hack”, “DejaVu Sans Mono”</li> </ol> <p>My Chromebook (screenshot) is set in Dutch, but it should give you an idea on where to find the correct fields.</p> <picture> <source srcset="/_astro/chromebook-terminal-settings.C0PR8dnS_216vuD.avif 320w, /_astro/chromebook-terminal-settings.C0PR8dnS_1nSy0N.avif 480w, /_astro/chromebook-terminal-settings.C0PR8dnS_Z1SeaVY.avif 800w, /_astro/chromebook-terminal-settings.C0PR8dnS_ZKEt59.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-terminal-settings.C0PR8dnS_ZurMtv.webp 320w, /_astro/chromebook-terminal-settings.C0PR8dnS_Z17EJXl.webp 480w, /_astro/chromebook-terminal-settings.C0PR8dnS_FoDRN.webp 800w, /_astro/chromebook-terminal-settings.C0PR8dnS_1MXlJD.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-terminal-settings.C0PR8dnS_2kWivp.png" srcset="/_astro/chromebook-terminal-settings.C0PR8dnS_2w9hg.png 320w, /_astro/chromebook-terminal-settings.C0PR8dnS_ZzFNcz.png 480w, /_astro/chromebook-terminal-settings.C0PR8dnS_1dnADz.png 800w, /_astro/chromebook-terminal-settings.C0PR8dnS_2kWivp.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Terminal settings on chromebook" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="624"> </picture> <p>And there you have it, You now have a beautiful terminal. If you’re not into the agnoster theme, there is <a href="https://dev.to/davidmorais/customize-chrome-os-linux-terminal-3ame" target="_blank" rel="noreferrer noopener">this awesome guide for the powerlevel theme as well</a>.</p> <h2 id="so-much-possibilities">So much possibilities</h2> <p>As you noticed, installing things on ChromeOS is a breeze and I suggest you look around a bit, I’m sure you’ll find that perfect setup that just works. It took me a bit of time but I had a lot of fun figuring these things out. Also good to know, you can easily backup your Linux via the settings in ChromeOS in case you doubt if you’ll break something. Happy coding!</p>Brecht De RuyteCoding on a Chromebook - part 1https://utilitybend.com/blog/coding-on-a-chromebook-part-1/https://utilitybend.com/blog/coding-on-a-chromebook-part-1/A few months ago, I purchased a Chromebook for my little side projects. I'm used to working on a Macbook, but I wanted to work with something that has a different feel, to make my little creations on the side feel more relaxing, and less like work. I have gained a lot of love for ChromeOS. This will be an article in two parts. In the first part, I want to talk about the first experiences, my basic ChromeOS setup and enabling developer mode.Sun, 31 Jul 2022 00:00:00 GMT<picture> <source srcset="/_astro/dino.YW6q02U0_Z267jWi.avif 375w, /_astro/dino.YW6q02U0_1jy1eF.avif 480w, /_astro/dino.YW6q02U0_ZcfyJT.avif 680w, /_astro/dino.YW6q02U0_Z1PYSLV.avif 800w, /_astro/dino.YW6q02U0_Z1z53Ru.avif 980w, /_astro/dino.YW6q02U0_Z2bbdRu.avif 1024w, /_astro/dino.YW6q02U0_18d61L.avif 1660w, /_astro/dino.YW6q02U0_Z2fXlrV.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/dino.YW6q02U0_Z2nIr5D.webp 375w, /_astro/dino.YW6q02U0_11VT6k.webp 480w, /_astro/dino.YW6q02U0_ZtQFSf.webp 680w, /_astro/dino.YW6q02U0_Z28B0Uh.webp 800w, /_astro/dino.YW6q02U0_Z1QGb0P.webp 980w, /_astro/dino.YW6q02U0_ZWDyti.webp 1024w, /_astro/dino.YW6q02U0_2lJKpX.webp 1660w, /_astro/dino.YW6q02U0_1ea6C.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/dino.YW6q02U0_1m8yhB.jpg" srcset="/_astro/dino.YW6q02U0_IjkR3.jpg 375w, /_astro/dino.YW6q02U0_ZVcrJU.jpg 480w, /_astro/dino.YW6q02U0_Z2s12Ju.jpg 680w, /_astro/dino.YW6q02U0_XqL2p.jpg 800w, /_astro/dino.YW6q02U0_1flAVQ.jpg 980w, /_astro/dino.YW6q02U0_Z100VcH.jpg 1024w, /_astro/dino.YW6q02U0_2jnnGy.jpg 1660w, /_astro/dino.YW6q02U0_Z1b8ns5.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Dino with heart" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="why-did-i-choose-a-chromebook">Why did I choose a chromebook?</h2> <p>They are small, lightweight, and most of all affordable. You can get a Chromebook for about 350EUR that has about <strong>8GB</strong> of RAM and a good enough processor to do some multitasking. Not being able to buy a pixelbook in Belgium, I chose to buy an ASUS Flip Chromebook.</p> <p>What I was going to use the device for was quite simple: Write my articles, do some small coding projects, maintain my blog and do all of this as distraction free as possible. For me, It was important to have a Chromebook that wasn’t too heavy or big, because I knew that I wanted to code with it around the house. This also means that battery life was an important feature.</p> <picture> <source srcset="/_astro/chromebook.Bw5NMlt5_1G7aMr.avif 320w, /_astro/chromebook.Bw5NMlt5_Z2pspvQ.avif 480w, /_astro/chromebook.Bw5NMlt5_Z2rwPmA.avif 800w, /_astro/chromebook.Bw5NMlt5_Z7eDiM.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook.Bw5NMlt5_1YwWjf.webp 320w, /_astro/chromebook.Bw5NMlt5_Z272D03.webp 480w, /_astro/chromebook.Bw5NMlt5_Z2973PM.webp 800w, /_astro/chromebook.Bw5NMlt5_bb8d1.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook.Bw5NMlt5_2n9uqK.jpg" srcset="/_astro/chromebook.Bw5NMlt5_mmhV2.jpg 320w, /_astro/chromebook.Bw5NMlt5_1kXPqF.jpg 480w, /_astro/chromebook.Bw5NMlt5_1iTpzV.jpg 800w, /_astro/chromebook.Bw5NMlt5_Z1qYwac.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="ASUS Chromebook" loading="lazy" decoding="async" fetchpriority="auto" width="4" height="3"> </picture> <h2 id="the-basic-things-about-chromeos-that-i-love">The basic things about ChromeOS that I love</h2> <p>Before writing about the coding process itself and how I created a basic setup, let me tell some of the things that I love about ChromeOS:</p> <h3 id="blazing-fast-in-starting-and-battery-life">Blazing fast in starting and battery life</h3> <p>Yep, unless your Macbook is fresh from the shelf, this difference is really noticeable. The device literally boots in a few seconds and you’re ready to browse or create a document using the Google services. I remember installing my code tools, which took about half a day. I did all of that on battery life…</p> <h3 id="great-in-organizing-your-workspaces">Great in organizing your workspaces</h3> <p>You can work full screen, barely noticing you’re running things inside of a browser. You can switch between fullscreen and windowed with the press of a dedicated button. There is also the possibility to use different desks on which you can toggle full screen with the simple short key <code>search + [</code> or <code>search + ]</code>.</p> <h3 id="works-wonderfully-with-my-android-phone">Works wonderfully with my Android phone</h3> <p>Unlocking the screen by using my phone, using my phone network automatically when there’s no internet available. Instantly having pictures of my phone available or even sending text messages straight out of the laptop. All these little things do wonders. It’s a great experience.</p> <h3 id="android-apps">Android apps</h3> <p>Although made for using the browser most of the time, You can easily install Android apps. My Chromebook has a screen that you can flip and use as a tablet. It’s a great addition, although not one that I use a lot.</p> <h3 id="linux">Linux</h3> <p>This was probably the most important thing to start coding on it. You will have to set up the developer mode and when you do that, you can run most of the things that work on Linux. More about that later on.</p> <h3 id="keep-those-updates-coming-google">Keep those updates coming Google!</h3> <p>Google updates are coming in a lot, and they don’t take long at all. Every little update gave a little smile to my face. It’s great to have an OS where updates feel like fun instead of delaying them because you don’t have the time for it.</p> <h2 id="the-basic-things-about-chromeos-that-take-getting-used-to">The basic things about ChromeOS that take getting used to</h2> <h3 id="hard-disk-space">Hard disk space</h3> <p>Let’s not delay this any longer and talk about the elephant in the room. ChromeOS should be used with cloud storage unless you want to take an external hard drive with you all the time. Remember I said, I’d use it for <em>“little side projects”</em>.</p> <h3 id="not-for-gaming">Not for gaming</h3> <p>Unless you use <strong>Stadia</strong>, it’s not a great device for gaming, unless you play Android games. Although Steam will get a release on ChromeOS in the future. This didn’t bother me, as this was not the purpose of my purchase.</p> <h3 id="you-will-need-alternatives-for-certain-programs">You will need alternatives for certain programs</h3> <p>If you happen to be a designer that relies on Adobe software, you will be disappointed. Unless you’re happy with using creative software in the browser. Figma works just fine of course.</p> <h3 id="the-keys-take-some-getting-used-to-and-maybe-one-is-missing">The keys take some getting used to, and maybe one is missing?</h3> <p>Want to use CAPS LOCK, you’ll have to press ALT+SEARCH. And you only have a ctrl and alt key on the keyboard. Which can make your coding shortcuts a bit out of touch. I think this last one was the biggest bother for me when I started out.</p> <picture> <source srcset="/_astro/chromebook-inside.CcS-dxE9_uAm04.avif 320w, /_astro/chromebook-inside.CcS-dxE9_1pjyer.avif 480w, /_astro/chromebook-inside.CcS-dxE9_Z2tB9Io.avif 800w, /_astro/chromebook-inside.CcS-dxE9_Z2tBabS.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-inside.CcS-dxE9_2kTBQw.webp 320w, /_astro/chromebook-inside.CcS-dxE9_Z1OyjI2.webp 480w, /_astro/chromebook-inside.CcS-dxE9_ZDhSQV.webp 800w, /_astro/chromebook-inside.CcS-dxE9_ZDhTkq.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-inside.CcS-dxE9_lsaV5.jpg" srcset="/_astro/chromebook-inside.CcS-dxE9_ySUYP.jpg 320w, /_astro/chromebook-inside.CcS-dxE9_1tC8ed.jpg 480w, /_astro/chromebook-inside.CcS-dxE9_Z2pizIC.jpg 800w, /_astro/chromebook-inside.CcS-dxE9_Z2piAc7.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Keyboard" loading="lazy" decoding="async" fetchpriority="auto" width="400" height="300"> </picture> <h2 id="my-basic-chromeos-setup">My basic ChromeOS setup</h2> <p>After setting up the wizard, I was pretty much good to go. By going to the personalize section in the settings, I added a background that I like (you can also upload a custom background if you want). The next thing I did was to set up my phone. This gives me direct access to my images or photos I took with my phone. You can also set up that the chromebook unlocks when you unlock your phone with smartlock, very nifty.</p> <picture> <source srcset="/_astro/chromebook-theme.x3NcLx2L_Z1hhT1e.avif 320w, /_astro/chromebook-theme.x3NcLx2L_Zrvip5.avif 480w, /_astro/chromebook-theme.x3NcLx2L_cYIXS.avif 800w, /_astro/chromebook-theme.x3NcLx2L_ZGHPMb.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/chromebook-theme.x3NcLx2L_1e90g1.webp 320w, /_astro/chromebook-theme.x3NcLx2L_23UARa.webp 480w, /_astro/chromebook-theme.x3NcLx2L_Z2lKuxN.webp 800w, /_astro/chromebook-theme.x3NcLx2L_1NI3u4.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-theme.x3NcLx2L_A52vG.png" srcset="/_astro/chromebook-theme.x3NcLx2L_uYhD.png 320w, /_astro/chromebook-theme.x3NcLx2L_PhzSM.png 480w, /_astro/chromebook-theme.x3NcLx2L_1uMChK.png 800w, /_astro/chromebook-theme.x3NcLx2L_A52vG.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Theme selection in chrome os" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="407"> </picture> <p>To start off, I didn’t add a lot of android apps. Just spotify and the twitter app I use. Fun fact: When listening to music, you get this handy little icon in your toolbar with media controls.</p> <picture> <source srcset="/_astro/chromebook-media-controls.DQ8dHv79_24iOcV.webp 320w, /_astro/chromebook-media-controls.DQ8dHv79_Z5s0co.webp 480w, /_astro/chromebook-media-controls.DQ8dHv79_1XxXgL.webp 800w, /_astro/chromebook-media-controls.DQ8dHv79_Zk76CN.webp 846w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-media-controls.DQ8dHv79_2jrHSs.png" srcset="/_astro/chromebook-media-controls.DQ8dHv79_Zmju4J.png 320w, /_astro/chromebook-media-controls.DQ8dHv79_Z2w5ju4.png 480w, /_astro/chromebook-media-controls.DQ8dHv79_Zs4l0T.png 800w, /_astro/chromebook-media-controls.DQ8dHv79_2jrHSs.png 846w" sizes="(max-width: 800px) 100vw, 80vw" alt="Media controls in chrome os" aspectRatio=" 980:397" loading="lazy" decoding="async" fetchpriority="auto" width="846" height="343"> </picture> <p>One thing that I like about ChromeOS is the UI. It’s all very easy to set it the way you want for basic usage, and that’s a good thing.</p> <p>Next up, I added some “desks” for my workflow. I like to work full screen, especially on a laptop screen. So I pre-configured some of the desks I will be using. There is a dedicated key for this and some shortcuts to control them. <a href="https://support.google.com/chromebook/answer/9594869?hl=en" target="_blank" rel="noreferrer noopener">Google has some good information on windows and desks</a>.</p> <figure class="image"><picture> <source srcset="/_astro/chromebook-workspaces.DZhqCxQY_28Uksp.webp 320w, /_astro/chromebook-workspaces.DZhqCxQY_ZKfd4i.webp 480w, /_astro/chromebook-workspaces.DZhqCxQY_Z3xGPj.webp 800w, /_astro/chromebook-workspaces.DZhqCxQY_q9akC.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-workspaces.DZhqCxQY_Z1PdoQc.png" srcset="/_astro/chromebook-workspaces.DZhqCxQY_Z1dvXss.png 320w, /_astro/chromebook-workspaces.DZhqCxQY_WuBNL.png 480w, /_astro/chromebook-workspaces.DZhqCxQY_1Ec82K.png 800w, /_astro/chromebook-workspaces.DZhqCxQY_28T0dG.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Workspaces or desks in chrome os" aspectRatio=" 980:550" loading="lazy" decoding="async" fetchpriority="auto" width="1920" height="1080"> </picture><figcaption>Desks on a chromebook</figcaption></figure> <p>Last thing I did after a while using it was to join the beta channel. This will add experimental features, so you might not need this, but there were some features that I wanted to try out such as the calendar pane. You can also set up experimental features by adding <code>chrome://flags</code> in your browser. To enter the beta channel, simply go to <code>about ChromeOS &gt; additional details &gt; channel</code> and set the channel to <code>beta</code>.</p> <figure class="image"><picture> <source srcset="/_astro/chromebook-calendar.Co6if1vq_Z27HKn3.webp 320w, /_astro/chromebook-calendar.Co6if1vq_ZiVcP9.webp 480w, /_astro/chromebook-calendar.Co6if1vq_ZkIM5O.webp 800w, /_astro/chromebook-calendar.Co6if1vq_5iqwX.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/chromebook-calendar.Co6if1vq_Z260jnY.png" srcset="/_astro/chromebook-calendar.Co6if1vq_2giCMv.png 320w, /_astro/chromebook-calendar.Co6if1vq_Z106Wtw.png 480w, /_astro/chromebook-calendar.Co6if1vq_Z11TwJc.png 800w, /_astro/chromebook-calendar.Co6if1vq_ZARj6p.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Calendar in chrome os" aspectRatio=" 980:550" loading="lazy" decoding="async" fetchpriority="auto" width="1920" height="1080"> </picture><figcaption>The calendar with additional settings screen</figcaption></figure> <p>I won’t go over every shortcut possible with a chromebook but there are quite a few that I use regularly, for taking screenshots or partial screenshots, switching between desks. You can <a href="https://support.google.com/chromebook/answer/183101?hl=en" target="_blank" rel="noreferrer noopener">find a handy shortkey list on this page</a>.</p> <p>When it comes to the shelf, I personally like it at the bottom of the screen but you can switch it to the right or left if you want to.</p> <h2 id="setting-up-the-linux-environment">Setting up the Linux environment</h2> <p>If you want to properly start coding on a ChromeOS, you’ll have to open up the developer mode.</p> <p>These are the steps you’ll have to take:</p> <ol> <li>On your Chromebook, at the bottom right, select the time. Or press the search button and search for settings.</li> <li>Select <code>advanced &gt; developers</code></li> <li>Next to “Linux development environment,” select Turn On.</li> <li>Follow the on-screen instructions. You will have to allocate some space for this. I added more than the recommended because I will be coding on this, and my projects will be stored in this allocated space.</li> <li>A terminal window opens. You have a Debian 10 (Buster) environment. You can run Linux commands, install more tools using the APT package manager, and customize your shell.</li> </ol> <p>In these settings you can also backup your linux environment and save it to google drive. This can be handy if you plan on experimenting a bit as I did. You can also remove the Linux environment again and everything “Linux” will be wiped clean.</p> <h2 id="running-vscode-on-your-chromebook">Running VSCode on your Chromebook</h2> <p>Now that we have our linux environment running, we can easily set up VSCode. Before we install our editor, let’s run a little update, I had to do this two times, not sure why.</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> update </code></pre> <p>Next, we need to install the gnome keyring:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> <span class="token parameter variable">-y</span> gnome-keyring </code></pre> <p>Once we did this, all we have to do is to <a href="https://code.visualstudio.com/download" target="_blank" rel="noreferrer noopener">download VSCode from the official website</a>.</p> <p>Ensure you <strong>select the correct package</strong> for your Chromebook, to avoid falling into errors while installing it on your Chromebook.</p> <ul> <li>If your Chromebook runs on an AMD or Intel chip, select the .deb 64bit variant.</li> <li>If your Chromebook runs on an ARM64 chip, select the .deb ARM64 variant.</li> </ul> <p>Not sure which version to download? You can check this by adding the following command in your terminal:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash">dpkg --print-architecture </code></pre> <p>When you downloaded the installer, just open it in the downloads folder and it will install inside the Linux environment.</p> <p><strong>Quick tip:</strong> You can pin VSCode to you shelf by simply right clicking the icon when it’s open and selecting “<em>pin to shelf</em>”.</p> <h2 id="thats-it-for-part-1">That’s it for part 1</h2> <p>This was a quick summary of the things I like about ChromeOS.</p> <p><a href="https://utilitybend.com/blog/coding-on-a-chromebook-part-2">In part 2</a> I am going a bit more in depth by installing a node version manager, filezilla, docker, some browsers, ohmyzsh and other goodies to make things easier.</p>Brecht De RuyteIntroducing a Mini Static Thingy Wingyhttps://utilitybend.com/blog/introducing-a-mini-static-thingy-wingy/https://utilitybend.com/blog/introducing-a-mini-static-thingy-wingy/The world is full of frameworks and content management systems, we're very spoiled when we want to create an application, but sometimes, you just need something static, simple and fast, so I created a Mini Static Thingy Wingy.Thu, 07 Jul 2022 00:00:00 GMT<picture> <source srcset="/_astro/visual.ZncRzMaI_Z2dTzpa.avif 375w, /_astro/visual.ZncRzMaI_1yEB6u.avif 480w, /_astro/visual.ZncRzMaI_8D1LK.avif 680w, /_astro/visual.ZncRzMaI_1SyMax.avif 800w, /_astro/visual.ZncRzMaI_14qTEN.avif 980w, /_astro/visual.ZncRzMaI_ZqwAAs.avif 1024w, /_astro/visual.ZncRzMaI_ZhXeAs.avif 1660w, /_astro/visual.ZncRzMaI_Z1pen80.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.ZncRzMaI_3hV9o.webp 375w, /_astro/visual.ZncRzMaI_Z1ek18S.webp 480w, /_astro/visual.ZncRzMaI_2pPxlj.webp 680w, /_astro/visual.ZncRzMaI_ZTpP4P.webp 800w, /_astro/visual.ZncRzMaI_Z1IxHzz.webp 980w, /_astro/visual.ZncRzMaI_ZRWzug.webp 1024w, /_astro/visual.ZncRzMaI_ZJodug.webp 1660w, /_astro/visual.ZncRzMaI_Z5Pnw6.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.ZncRzMaI_eKXrb.jpg" srcset="/_astro/visual.ZncRzMaI_Z194Bpj.jpg 375w, /_astro/visual.ZncRzMaI_Z2qGyHA.jpg 480w, /_astro/visual.ZncRzMaI_1dsYLB.jpg 680w, /_astro/visual.ZncRzMaI_Z26MnDx.jpg 800w, /_astro/visual.ZncRzMaI_29gREE.jpg 980w, /_astro/visual.ZncRzMaI_2vavpm.jpg 1024w, /_astro/visual.ZncRzMaI_Z2qsgoz.jpg 1660w, /_astro/visual.ZncRzMaI_Z1BTprp.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Little purple head with lightning around it - static thingy wingy logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="what-is-it-about">What is it about?</h2> <p>In a world full of frameworks, packages and guidelines, it’s sometimes a bit of an overkill to use all of these frameworks. Sometimes you just want to create a simple page or a sandbox to play in.</p> <p>The Mini Static Thingy Wingy was created with just one thing in mind: An easy setup for a simple HTML and CSS driven page. This could be a landing page, a small demo, or an HTML presentation.</p> <p>By using <a href="https://twig.symfony.com/" target="_blank" rel="noreferrer noopener">Twig</a> as a templating engine and <a href="https://postcss.org/" target="_blank" rel="noreferrer noopener">PostCSS</a> + SCSS to handle the styling, it’s a very simple setup. The Thingy is not meant to be the next new thing, it’s meant to be mini, and static, to create something on the fly (wing it).</p> <a href="https://github.com/brechtDR/mini-static-thingy-wingy" target="_blank" rel="noreferrer noopener">View Mini Static Thingy Wingy on GitHub</a> <h2 id="why-create-this">Why create this?</h2> <p>It’s definitely not something new, and a lot of things are borrowed. The reason I created this is because it happens occasionally that I just need to create a little landing page. Sometimes I just want to set up something fast to create a page, or maybe two, or make a demo. And at times like that, I just want to get started fast. I used something similar like this before but I cleaned it up, added PostCSS in the mix and thought it would be nice to share.</p> <p>From my job at <a href="https://www.iodigital.com/nl" target="_blank" rel="noreferrer noopener">iO</a>, I’m used to working in Twig alongside with React. For such a small page, I always found React to be a bit overkill, so I chose Twig as a templating engine. This creates the possibility to create some partials, keep my files clean and transfer some variables between them.</p> <h2 id="how-to-run-a-mini-static-thingy-wingy">How to run a Mini Static Thingy Wingy</h2> <p>Make sure you have the latest node version installed. Simply <a href="https://github.com/brechtDR/mini-static-thingy-wingy" target="_blank" rel="noreferrer noopener">get the main directory</a> and run:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> </code></pre> <p>Now you are ready to build! To start watching you files for changes, run the following:</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> run dev </code></pre> <p>This will start the server at <code>localhost:5000</code> and you’ll already have a live reload on your <code>index.twig</code> file and your <code>.scss</code> file. In the next part, i’ll go a bit more in-depth, but don’t worry, it’s easy</p> <p>When you’re happy with your result and want to build this, just add the following command which will create a build folder at the root of you projects, with your HTML, CSS, JS files (and other assets):</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> run build:production </code></pre> <p><strong>Note:</strong> The CSS is automatically injected before the closing of the <code>&lt;head&gt;</code> tag, while JS is automatically added before the closing of the <code>&lt;body&gt;</code> tag.</p> <h2 id="how-to-build-pages">How to build pages</h2> <p>Ofcourse, just building one file isn’t enough, here are some of the things you can do to build your static website.</p> <h3 id="creating-template-files">Creating template files</h3> <p>In the <code>src</code> folder of your project, you can find a <code>templates.json</code> file. When you first open the project, it will look like this:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string-property property">&quot;filename&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.html&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;template&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./templates/index.twig&quot;</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> </code></pre> <p>Whenever you want to create a new html file in your build, you’ll have to add another line. The first line is the output filename, and the second one is the input file.</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string-property property">&quot;filename&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.html&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;template&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./templates/index.twig&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">&quot;filename&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./my-new-file.html&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;template&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./templates/original-new-file.twig&quot;</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> </code></pre> <h3 id="other-commands">Other commands</h3> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> run build </code></pre> <p>Will build the files without minifying or combining CSS. This is a development output.</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> run <span class="token function">watch</span> </code></pre> <p>Will build the files without minifying or combining CSS and watch for changes, will rebuild on change. This is slower than running the dev server.</p> <pre class="language-bash" data-language="bash"><code is:raw="" class="language-bash"><span class="token function">npm</span> run install:demo </code></pre> <p>This will install some demo files with examples and start the server.</p> <p><strong>!!! Be careful, as this WILL overwrite items in your src folder !!!</strong></p> <h2 id="why-twig-postcss-and-vanilla-js">Why Twig, PostCSS and Vanilla JS?</h2> <h3 id="twig">Twig</h3> <p><a href="https://twig.symfony.com/" target="_blank" rel="noreferrer noopener">Twig</a> is a templating language by Symfony. It’s well known for it’s implementations in <a href="https://craftcms.com/" target="_blank" rel="noreferrer noopener">Craft CMS</a> and <a href="https://www.drupal.org/" target="_blank" rel="noreferrer noopener">Drupal</a>. Twig felt like the right choice for me because first of all, I’m used to it and second of all, it still feels like writing HTML</p> <p>Twig also makes it easy to set variables for this Mini Static Thingy Wingy since it allows you to pass variables like so:</p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">include</span> <span class="token string"><span class="token punctuation">&quot;</span>./partials/_navigation.twig<span class="token punctuation">&quot;</span></span><span class="token punctuation">,</span> with <span class="token punctuation">{</span> active<span class="token punctuation">:</span> <span class="token string"><span class="token punctuation">&quot;</span>home<span class="token punctuation">&quot;</span></span> <span class="token punctuation">}</span> <span class="token delimiter punctuation">%}</span></span> </code></pre> <h3 id="postcss">PostCSS</h3> <p>I love to play around with <a href="https://postcss.org/" target="_blank" rel="noreferrer noopener">PostCSS</a> and all it has to offer. You can add your own custom packages in the <code>postcss.config.js</code> file at the root of the project.</p> <p>In the production build, I added the <a href="https://purgecss.com/plugins/postcss.html" target="_blank" rel="noreferrer noopener">purgeCSS</a> and <a href="https://github.com/leodido/postcss-clean" target="_blank" rel="noreferrer noopener">PostCSS Clean</a> plugins, because they make sense to me, but if you’d rather see those go, you can always go into the config file and delete them.</p> <h3 id="pure-simple-javascript">Pure simple Javascript</h3> <p>Because we want to keep it small and clean, besides… Vanilla JS is a lot of fun. I added <a href="https://babeljs.io/" target="_blank" rel="noreferrer noopener">Babel</a> and some plugins to make life a bit more easy such as dynamic import and transform runtime. You can always add more if you like.</p> <h2 id="working-with-assets-and-translations">Working with assets and translations</h2> <p>If you want to create something bigger, it’s still possible, however, this little Thingy wasn’t meant to do that. You can play around with subfolders or even add translations files in the form of a json-file.</p> <h3 id="assets-and-subfolders-for-templates">Assets and subfolders for templates</h3> <p>When using relative paths to your tabs, it can get harder when you want to use subfolders. By default a variable is set in the <code>_base.html</code> which sets the base path of assets to <code>&quot;./assets/&quot;</code>.</p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> assets <span class="token operator">=</span> assets<span class="token operator">|</span>default<span class="token punctuation">(</span><span class="token string"><span class="token punctuation">&quot;</span>./assets/<span class="token punctuation">&quot;</span></span><span class="token punctuation">)</span> <span class="token delimiter punctuation">%}</span></span> </code></pre> <p>When using subfolders it might get handy to update this variable to a new relative path such as:</p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> assets <span class="token operator">=</span> <span class="token string"><span class="token punctuation">&quot;</span>../assets/<span class="token punctuation">&quot;</span></span> <span class="token delimiter punctuation">%}</span></span> </code></pre> <h3 id="using-translation-files">Using translation files</h3> <p>You can upgrade the webpack config to look for translation files in your folder.</p> <p><strong>1. In you webpack, add the following line of code</strong></p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">let</span> templatePages <span class="token operator">=</span> _<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>pages<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">page</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">//new line of code below</span> <span class="token keyword">let</span> translation <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>__dirname<span class="token punctuation">,</span> <span class="token string">&quot;src&quot;</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">&#39;/translations/lang.&#39;</span><span class="token operator">+</span>page<span class="token punctuation">.</span>lang<span class="token operator">+</span><span class="token string">&#39;.json&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// etc</span> </code></pre> <p><strong>2. When creating a template add a language parameter for example:</strong></p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string-property property">&quot;filename&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.html&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;template&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./templates/index.twig&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;lang&quot;</span><span class="token operator">:</span> <span class="token string">&quot;en&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token string-property property">&quot;filename&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index-fr.html&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;template&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./templates/index.twig&quot;</span><span class="token punctuation">,</span> <span class="token string-property property">&quot;lang&quot;</span><span class="token operator">:</span> <span class="token string">&quot;fr&quot;</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span> </code></pre> <p>Now we will create two pages from the same template, which will look at a different translation file (eg:<code>lang.fr.json</code> and <code>lang.en.json</code>)</p> <p><strong>3. Let’s create those two translation files in src/translations and add a translation with the same key.</strong></p> <p>In translations/lang.en.json</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token punctuation">{</span> <span class="token string-property property">&quot;hello&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Hello world&quot;</span> <span class="token punctuation">}</span> </code></pre> <p>in translations/lang.fr.json</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token punctuation">{</span> <span class="token string-property property">&quot;hello&quot;</span><span class="token operator">:</span> <span class="token string">&quot;Bonjour le monde&quot;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>4. In our _base.twig template, we can now set a global variable</strong></p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"><span class="token twig language-twig"><span class="token delimiter punctuation">{%</span> <span class="token tag-name keyword">set</span> t <span class="token operator">=</span> htmlWebpackPlugin<span class="token punctuation">.</span>options<span class="token punctuation">.</span>trans <span class="token delimiter punctuation">%}</span></span> </code></pre> <p><strong>5. And in our extended templates we can just refer to those variables as such:</strong></p> <pre class="language-twig" data-language="twig"><code is:raw="" class="language-twig"> <span class="token twig language-twig"><span class="token delimiter punctuation">{{</span> t<span class="token punctuation">.</span>hello <span class="token delimiter punctuation">}}</span></span> </code></pre> <p>You should now get an output with different files in different languages, using the same base template.</p> <h2 id="thoughts">Thoughts…</h2> <p>So, I created this mostly for personal use, but I hope maybe someone else can find a benefit in it. It’s an easy setup and I think it could be great for beginners that are just starting to learn HTML and CSS and find a bit of insight in the Twig templating language. I had fun creating it and will probably use it in the future if I need to create a “quick page”. If it is broken, feel free to <a href="https://github.com/brechtDR/mini-static-thingy-wingy/issues" target="_blank" rel="noreferrer noopener">file an issue on GitHub</a> and I’ll see what I can do. Even if you have an idea on how to upgrade it, feel free to create a pull request. But remember, it has to stay Tiny ;)</p>Brecht De RuyteCSS Day 2022: A small recaphttps://utilitybend.com/blog/css-day-2022-a-small-recap/https://utilitybend.com/blog/css-day-2022-a-small-recap/After a long break because of Covid-19, CSS Day is back. In a new location at the Zuiderkerk in Amsterdam. Once again trying to create the perfect line-up for everything design and CSS, and boy, they delivered just that.Sat, 11 Jun 2022 00:00:00 GMT<picture> <source srcset="/_astro/css-day-visual.BWXouvYQ_qlcKz.avif 375w, /_astro/css-day-visual.BWXouvYQ_2w7Ude.avif 480w, /_astro/css-day-visual.BWXouvYQ_2woLLR.avif 680w, /_astro/css-day-visual.BWXouvYQ_2vi1Xh.avif 800w, /_astro/css-day-visual.BWXouvYQ_1YMkAC.avif 980w, /_astro/css-day-visual.BWXouvYQ_1cO1Am.avif 1024w, /_astro/css-day-visual.BWXouvYQ_WJdUm.avif 1660w, /_astro/css-day-visual.BWXouvYQ_Z2nzzBz.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/css-day-visual.BWXouvYQ_Z1LMkU6.webp 375w, /_astro/css-day-visual.BWXouvYQ_iYmwy.webp 480w, /_astro/css-day-visual.BWXouvYQ_jge6c.webp 680w, /_astro/css-day-visual.BWXouvYQ_i9thB.webp 800w, /_astro/css-day-visual.BWXouvYQ_Zdld53.webp 980w, /_astro/css-day-visual.BWXouvYQ_Z1lVcVk.webp 1024w, /_astro/css-day-visual.BWXouvYQ_Z1B10Bk.webp 1660w, /_astro/css-day-visual.BWXouvYQ_ZFSE7X.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/css-day-visual.BWXouvYQ_1xIsz8.jpg" srcset="/_astro/css-day-visual.BWXouvYQ_Z1FGYBu.jpg 375w, /_astro/css-day-visual.BWXouvYQ_p4HPa.jpg 480w, /_astro/css-day-visual.BWXouvYQ_plzoN.jpg 680w, /_astro/css-day-visual.BWXouvYQ_oeOAd.jpg 800w, /_astro/css-day-visual.BWXouvYQ_Z7fQLr.jpg 980w, /_astro/css-day-visual.BWXouvYQ_1ILBvt.jpg 1024w, /_astro/css-day-visual.BWXouvYQ_1tGNPt.jpg 1660w, /_astro/css-day-visual.BWXouvYQ_Z1ooKxO.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="CSS Day conference room" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="waking-up-in-the-beautiful-amsterdam">Waking up in the beautiful Amsterdam</h2> <p>Every time I go to a hotel, I seem to struggle the first night with getting some sleep. Waking up way too early to head off to the conference, I decided to go for a morning run, the sun was just starting to peep through the trees, letting me know that today will be a good day.</p> <picture> <source srcset="/_astro/morning-run.B1fle85b_Z1gA5KL.avif 320w, /_astro/morning-run.B1fle85b_Z1TUWVu.avif 480w, /_astro/morning-run.B1fle85b_Z1LLeNt.avif 800w, /_astro/morning-run.B1fle85b_2rk1qk.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/morning-run.B1fle85b_Z24SeeJ.webp 320w, /_astro/morning-run.B1fle85b_2lX2ot.webp 480w, /_astro/morning-run.B1fle85b_2u7Kwu.webp 800w, /_astro/morning-run.B1fle85b_1D1RWm.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/morning-run.B1fle85b_2qCH3x.jpg" srcset="/_astro/morning-run.B1fle85b_Z1CjiQ0.jpg 320w, /_astro/morning-run.B1fle85b_Z2gEb1I.jpg 480w, /_astro/morning-run.B1fle85b_Z28urSH.jpg 800w, /_astro/morning-run.B1fle85b_25ANl6.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Sunrise in Amsterdam" loading="lazy" decoding="async" fetchpriority="auto" width="245" height="153"> </picture> <h2 id="about-the-venue-of-css-day">About the venue of CSS Day</h2> <picture> <source srcset="/_astro/venue.CvOBtw3U_16C3ef.avif 320w, /_astro/venue.CvOBtw3U_ZCbkLF.avif 480w, /_astro/venue.CvOBtw3U_Kpm3H.avif 800w, /_astro/venue.CvOBtw3U_hcsVR.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/venue.CvOBtw3U_2k9HCr.webp 320w, /_astro/venue.CvOBtw3U_AljBw.webp 480w, /_astro/venue.CvOBtw3U_Z22zgbF.webp 800w, /_astro/venue.CvOBtw3U_Z2vM9iv.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/venue.CvOBtw3U_Z1y39oP.jpg" srcset="/_astro/venue.CvOBtw3U_2hMkT2.jpg 320w, /_astro/venue.CvOBtw3U_xXVS7.jpg 480w, /_astro/venue.CvOBtw3U_1Pfk3y.jpg 800w, /_astro/venue.CvOBtw3U_1m2qVI.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Sunrise in Amsterdam" loading="lazy" decoding="async" fetchpriority="auto" width="720" height="960"> </picture> <p>Now being hosted in the Zuiderkerk, a fantastic location. When I arrived, to my surprise, I was welcomed by the clocks of the church. But strangely enough, when you are inside of the church you can barely hear them. I’m not the church-going type of person, let alone, two days in a row. But I do know a bit about acoustics and they really did a lot of effort when it comes to sound, except for the beautiful architecture, you could barely notice that you were sitting in church. After a hard deserved croissant after my morning run, It was time to take places and start the conference.</p> <h2 id="the-speakers-at-day-1">The speakers at day 1</h2> <h3 id="jeremy-keith-in-and-out-of-style">Jeremy Keith: In And Out Of Style</h3> <p>Quite a known speaker, he’s been around for some time and gave a great keynote about the current and upcoming exciting times for CSS. With a blast from the past, showing us how third party libraries and hacks can become standards, or at least become reasons for standards to be created. I felt a bit of nostalgia listening to that talk, especially when he was talking about rounded corners in the Internet Explorer 6 era. Showing off <a href="https://worldwideweb.cern.ch/timeline/">his work and on the WorldWideWeb NeXT timeline</a> and explaining a few of those points of history and last but not least <a href="https://principles.adactio.com/">sharing his love for design systems</a>. A great talk to start the day.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/CdZZcbZG83o?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="In And Out Of Style | Jeremy Keith | CSS Day 2022" style=""></iframe></div> <h3 id="rachel-andrew-interop-2022">Rachel Andrew: Interop 2022</h3> <p>The so-called “browser wars” are a thing of the past. Nowadays, all browser vendors come together to solve the top browsers compatibility issues identified by developers. This is <a href="https://web.dev/interop-2022/">interop 2022</a> and if you haven’t checked it out, I advise you to do so. It’s interesting to see the main working areas on browsers and also the dedication to first fix the things already implemented such as the gap property for Flexbox. A lot of the Interop 2022 items would become more clear during the day, but It was interesting to hear how these things get measured, about tests and also that we still have a chance to influence priorities for Interop 2023 by <a href="https://github.com/web-platform-tests/interop-2022/issues/">creating and starring issues on Github</a>. The future of CSS is bright.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/w_gOOW2ARMk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Interop 2022 | Rachel Andrew | CSS Day 2022" style=""></iframe></div> <h3 id="lea-verou-css-variable-secrets">Lea Verou: CSS Variable Secrets</h3> <p>The first in-depth tech talk of the day. There was clearly a lot to say about using CSS variables. This is so much more than just SASS variables in native CSS. From creating booleans out of variables to some more complex algebra and showing us that CSS Custom Properties can hold just about any value. This was one of those talks that made me say: wait? You can actually do that? <a href="https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/#more-3162">Creating space toggles in variables to create progressive enhancement</a> for the new color modes in CSS, now that is a clever hack. Her talk also made me realise how badly I want to play around with the style container queries in the future. I know that this talk made me want to try some things out with variables and custom properties. So much information, so little time.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/ZuZizqDF4q8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="CSS Variable Secrets | Lea Verou | CSS Day 2022" style=""></iframe></div> <h3 id="bramus-van-damme-the-css-cascade-a-deep-dive">Bramus Van Damme: The CSS Cascade, a deep dive</h3> <p>It’s nice to see a Belgian up on that stage, as a former lecturer at Odisee you can clearly see the teaching skills of Bramus. With an audience still recovering from a lunch break he was able to keep the attention while going in depth about the cascade. He already <a href="https://www.bram.us/2021/09/15/the-future-of-css-cascade-layers-css-at-layer/">wrote a blogpost about this</a> which I read some time ago. But hearing him speak about it in combination with beautiful crafted slides made my enthusiasm grow just that little bit more. Excellent talk about this feature that is rolling out in all major browsers right now. The love for cascade layers is real and from a personal standpoint, I’m really happy about the syntax of this feature, it’s simpel, elegant, and easy to understand, especially after you understand the cascade just that little bit better. Thanks Bramus, keep talking with those hands!</p> <picture> <source srcset="/_astro/bramus.C-cyhpOx_ZBNXnT.avif 320w, /_astro/bramus.C-cyhpOx_24F9SP.avif 480w, /_astro/bramus.C-cyhpOx_Z1gSnF6.avif 800w, /_astro/bramus.C-cyhpOx_6hj3p.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/bramus.C-cyhpOx_1EnxaE.webp 320w, /_astro/bramus.C-cyhpOx_ZIjslx.webp 480w, /_astro/bramus.C-cyhpOx_10j7Ss.webp 800w, /_astro/bramus.C-cyhpOx_2ntOBX.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/bramus.C-cyhpOx_WPeQa.jpg" srcset="/_astro/bramus.C-cyhpOx_s0YAW.jpg 320w, /_astro/bramus.C-cyhpOx_Z1UG0Uf.jpg 480w, /_astro/bramus.C-cyhpOx_Zc3pFf.jpg 800w, /_astro/bramus.C-cyhpOx_1b7h3g.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Bramus on stage explaining the cascade" loading="lazy" decoding="async" fetchpriority="auto" width="245" height="153"> </picture> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/zEPXyqj7pEA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="The CSS Cascade, a deep dive | Bramus Van Damme | CSS Day 2022" style=""></iframe></div> <h3 id="ana-ferreira-collaborating-without-borders">Ana Ferreira: Collaborating without Borders</h3> <p>Ana Ferreira is Head of design at Doist, and has been working remotely for the company for about 8 years. She clearly explained that remote work is not the same as working from home, especially compared to working from home during the Covid pandemic. Asynchronous tools were key here, and such reducing meetings that could’ve been an email or a ticket. A lot of her talk was about trusting employees and taking a human first approach. I felt kind of jealous when she spoke about never having to log hours and just making sure that the work gets done. I can relate to this. An agenda shouldn’t always be stuffed with tasks. When the job gets done a bit more early, it might be interesting to work on personal development and growth, which in the end benefits a company. Because you can present your newly discovered technologies and maybe make the world/company a better place.</p> <blockquote> <p>“Let’s not use being overworked as a badge of honor. Set boundaries and prioritize your well-being” <cite>Anna Ferreira</cite></p> </blockquote> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/i6RmVPLFqeg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Collaborating without Borders | Ana Ferreira | CSS Day 2022" style=""></iframe></div> <h3 id="chris-lilley-escaping-the-srgb-prison">Chris Lilley: Escaping the sRGB Prison</h3> <p>I was actually looking forward to this talk because it was only a few weeks back since I chose to start reading the <a href="https://www.w3.org/TR/css-color-5/">color modules 5 draft</a> on the w3c website. And to be completely honest, colors are hard. I had a tough time understanding what was going on. But Chris Lilley delivered big time. Really showing how the gamut works and how different color syntaxes can give us different results was eye opening. One thing is for sure, in the future, gradients will look a lot more pretty. It’s an interesting module but also with a note of caution. Because we are going outside the sRGB prison, colors may become a bit more subjective. I might experience a color to be “greenish”, while another person might find it a bit more blue with a bit of green in it. Very interesting stuff. And for once we can say the following: There is no support for this in any browser, except for Safari, which supports all of those features!</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/zkun6wAPc1s?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Escaping the sRGB Prison | Chris Lilley | CSS Day 2022" style=""></iframe></div> <h3 id="marcin-wichary-i-pressed-b-you-wouldnt-believe-what-happened-next">Marcin Wichary: I pressed ⌘B. You wouldn’t believe what happened next</h3> <p>I don’t have a lot of experience when it comes to Figma, so I didn’t know what to expect from this talk. But “OMG”, this was a talk to be remembered. Marcin came on stage with an enthusiasm and a well crafted presentation that blew everyone away. I’ve always loved a good typography talk but this was next level. Talking about the struggles of font uses, the different weights, versions, sup and sub fonts, the struggle it takes to just press cmd+B to make a font bold. This was eye-opening. What is bold? If a font has a base weight of 400, but the bold version has 900, is using cmd+b to make it bold still a good idea? Is it just too bold and should it be considered as something else? And what do you do with all the other modifier keys on the keyboard? What about the legacy of numpads keys in figma to zoom? I personally never really thought of these things when it comes to design files and it was a great way to end the day. This was the surprise of CSS Day 1 for me. You can find an <a href="https://www.youtube.com/watch?v=kVD-sjtFoEI">alternate version of the talk</a>, but we got to see a lot more and enjoyed it very much.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/vUlnxjuYyzw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="I pressed ⌘B. You wouldn’t believe what happened next | Marcin Wichary | CSS Day 2022" style=""></iframe></div> <h2 id="the-speakers-at-day-2">The speakers at day 2</h2> <h3 id="adam-argyle-oh-snap">Adam Argyle: Oh Snap!</h3> <p>A little snap of the fingers and away we go for day 2. I was really looking forward to this because I had been working on accessible scroll tabs a while back. Adam made a beautiful presentation made with scroll snaps, as it should be ;). Really showing the full potential of scroll snapping beyond your basic slider. Using the scroll snap alignment to place the last item of a chat window at the bottom, so on page load it will always be visible. Using sticky positioning to create an overflow effect, and a lot more. He ended with a few things that they are working on in the <a href="https://drafts.csswg.org/css-scroll-snap-2/">scroll snap module 2</a> which among other things includes a :snapped pseudo element to target the current snapped item. Lovely stuff. Ow yeah, and I might need to revisit my demo just a little bit. Oh, and an interesting sidenote, together with Tab Atkins he’s also working on the <a href="https://www.w3.org/TR/css-nesting-1/">CSS nesting module</a>.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/34zcWFLCDIc?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Oh Snap! | Adam Argyle | CSS Day 2022" style=""></iframe></div> <h3 id="michelle-barker-creative-css-layout">Michelle Barker: Creative CSS Layout</h3> <p>Michelle started off with one of the questions that all of the css developers asked themselves before: In this use-cas, would you use Flex or Grid. And the answer can really depend on the use-case, you should always choose what feels right. She also showed some of the strengths in using the aspect-ratio property. For example when using it on a grid itself with images inside of it. A lot more of these nifty grid tricks such as creating fluid padding around your grid and let’s not forget container queries and fluid typography. A great tool for fluid typography named <a href="https://utopia.fyi/">utopia.fyi</a> is something i’ll take a look at very soon</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/tueTFd2TQUA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Creative CSS Layout | Michelle Barker | CSS Day 2022" style=""></iframe></div> <h3 id="amit-sheen-getting-creative-with-keyframes">Amit Sheen: Getting Creative with Keyframes</h3> <p>Animations, animations, ANIMATIONS! We are getting spoiled at CSS Day this year with a variety of topics. Amit Sheen creates some of the greatest CSS animations out there. He does note that some of his animations aren’t to be used in a live environment due to the performance issues they could bring. But giving an insight on how he starts working on them was a great thing to see. It sounds simple but he starts with pen and paper, writing multiple graphs to align animations to each other, by writing curves on the graph to indicate the easing of them. Using a negative delay on animations so that you don’t get the “hard start” when an animation begins. Another really clever thing is to use @property to animate multiple transforms at the same time. A lot to take home from and some best practises that i’m sure to use in the future.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/kXh3EMpaLFo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Getting Creative with Keyframes | Amit Sheen | CSS Day 2022" style=""></iframe></div> <h3 id="ben-evans-the-joy-of-css">Ben Evans: The Joy of CSS</h3> <p>If you don’t know Ben Evens, I strongly suggest you <a href="https://codepen.io/ivorjetski">take a look a at his work</a>.<br> This talk really felt a bit like watching Bob Ross on television, very calming, live painting in CSS. It was very relaxing to see, but to be honest, maybe a bit too relaxing so shortly after the lunch break. Still it was wonderful to see how he starts with creating his CSS paintings and the dedication it takes to make something, a half hour a day, and about a year till completion. But why does he do that? To quote Ben:</p> <blockquote> <ul> <li>“You can’t master something without knowing it’s limits”</li> <li>It’s fun and relaxing</li> </ul> </blockquote> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/Ey4ek7GK0OE?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="The Joy of CSS | Ben Evans | CSS Day 2022" style=""></iframe></div> <h3 id="maike-klip-service-design-and-front-end-interaction">Maike Klip: Service Design and Front End Interaction</h3> <p>When designing for services, what are the things you need to think about. In the past you had to actually go to a physical place for all your government related issues, nowadays the “government” is more becoming a computer rather than a physical person you can talk to. This brings a lot of frustration. How can you make a computer have a compassionate relation to its users? Maike has documented <a href="https://miro.com/app/board/o9J_lcpygEU=/">all her interactions with the government for over an entire month</a> and translated the key values into: desirable, viable, responsible, technological.</p> <p>She also talked about the <a href="https://dwarshuis.com/aardbevingen-groningen/visualisatie/view/?lang=en">earthquakes in Groningen</a> where people could get a subsidy for repairs or strengthening their houses. This resulted in enormous waiting lines, not only in real life, but also on the web. A badly created website, where by changing the URL you could get another spot on the waiting list and another person would be kicked out instead; When you hopefully/eventually got in after hours of waiting you were treated to a very bad crafted form that could even make a tech person scared for filling it in.</p> <picture> <source srcset="/_astro/Maike-klip.BIw7UwhI_Z1r7nBv.avif 320w, /_astro/Maike-klip.BIw7UwhI_1xHLBR.avif 480w, /_astro/Maike-klip.BIw7UwhI_Z1c9H3M.avif 800w, /_astro/Maike-klip.BIw7UwhI_ZKOLAO.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/Maike-klip.BIw7UwhI_Z18GB5H.webp 320w, /_astro/Maike-klip.BIw7UwhI_1Q8y8F.webp 480w, /_astro/Maike-klip.BIw7UwhI_ZSIUwY.webp 800w, /_astro/Maike-klip.BIw7UwhI_Zsp051.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/Maike-klip.BIw7UwhI_10Av02.jpg" srcset="/_astro/Maike-klip.BIw7UwhI_2jjRl1.jpg 320w, /_astro/Maike-klip.BIw7UwhI_dWSKs.jpg 480w, /_astro/Maike-klip.BIw7UwhI_Z2vTzUc.jpg 800w, /_astro/Maike-klip.BIw7UwhI_Z25zEse.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Maike Klip on stage" loading="lazy" decoding="async" fetchpriority="auto" width="245" height="153"> </picture> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/t86HIvEOL5A?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Service Design and Front End Interaction | Maike Klip | CSS Day 2022" style=""></iframe></div> <h3 id="tab-atkins-bittner-the-future-of-organizing-your-css">Tab Atkins-Bittner: The Future of Organizing Your CSS</h3> <p>Pages have become a lot more complex over time. The time that we used a simple selector is long gone. Now you could give everything a unique class in your CSS or you could nest a lot of levels deep, but in the end, you know you’ll end up with an “!important” slapped on top of it.</p> <p>You could fix that by just giving up and using a framework such as Tailwind or Bootstrap. But Tab thinks that we can do better. By using the shadow DOM to reduce long pages and scoping CSS inside of little components we could make pages significantly smaller in size. You can import a stylesheet inside of your components but what if you need to style them from the outside. Well, with some variable trickery you can make that happen. I loved how he created a dice-roll component, because he plays D&amp;D.</p> <h4 id="you-can-help-with-the-nesting-module">You can help with the nesting module!</h4> <p>He also talked about the CSS nesting module and they need your help. I wasn’t going to add code in this article because I feel like I should play around with some of the topics instead of just copying code from slides. However, they need our help with the nesting module. <a href="https://github.com/w3c/csswg-drafts/issues/4748">You can join the discussion of the nesting module here</a>. And here are the current ideas:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.a</span> <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 selector">&amp; .b</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@nest</span> .c &amp;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@nest</span> &amp;.d<span class="token punctuation">,</span> .e</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.a</span> <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 atrule"><span class="token rule">@nest</span> &amp; .b</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@nest</span> .c &amp;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@nest</span> &amp;.d<span class="token punctuation">,</span> .e</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.a</span> <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> <span class="token selector">&amp; .b</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token selector">.c &amp;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token selector">&amp;.d, .e</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="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/bz0sMsCiU1c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="Keeping Your CSS Small: scopes, containers, and other new stuff | Tab Atkins-Bittner | CSS Day 2022" style=""></iframe></div> <h3 id="stephen-hay-when-design-systems-lie">Stephen Hay: When Design Systems Lie</h3> <p>Do design systems alway lead to a good design? From that starting point Stephen Hay was going to start with telling some of the things that we lie about, mostly to ourselves. Design decisions are part of a system and often do not resemble the reality of the full design. It’s a system in a system. What this talk was really about is communication really. We need to talk with designers, show them what is possible in CSS. We don’t do that enough and there are a lot of cool things we can do nowadays which could impact design decisions.</p> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/-n5MSLg0yak?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="When Design Systems Lie | Stephen Hay | CSS Day 2022" style=""></iframe></div> <h2 id="conclusion">Conclusion</h2> <p>So, why did I not go into detail of all these talks, maybe I wasn’t paying attention at all?</p> <p>No, that’s not the case. First of all, I’m sure many will write an in-depth review of this event with code examples, links to slides and all of that. But I’m going to hold on to my notes for now, let things sink in, maybe create a few Codepens, try stuff out, re-read some of the things the speakers were talking about and maybe highlight something of it in the coming months.</p> <p>You can always look some of these speakers up or go to an event yourself. There is a lot of information out there and I encourage you to do so. Because even after hearing them speak, I’ll be doing the same, going in depth, learning.</p> <p>This was an amazing CSS Day edition, there was so much variety, I had some really interesting conversations as well with some of the speakers. It was a real “brain overload” at the end of it.</p> <p>And guess what, they’ll be back next year! On the 8th and 9th of june.</p> <picture> <source srcset="/_astro/the-end-of-css-day.D3S5yUU__z8VQ9.avif 320w, /_astro/the-end-of-css-day.D3S5yUU__ZGiBBp.avif 480w, /_astro/the-end-of-css-day.D3S5yUU__Z1Nmh44.avif 800w, /_astro/the-end-of-css-day.D3S5yUU__1IIVBD.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/the-end-of-css-day.D3S5yUU__1HJpv6.webp 320w, /_astro/the-end-of-css-day.D3S5yUU__rhQ2x.webp 480w, /_astro/the-end-of-css-day.D3S5yUU__ZEKNp7.webp 800w, /_astro/the-end-of-css-day.D3S5yUU__Z2cQIxl.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/the-end-of-css-day.D3S5yUU__Z5LFdV.jpg" srcset="/_astro/the-end-of-css-day.D3S5yUU__Z2jBp4T.jpg 320w, /_astro/the-end-of-css-day.D3S5yUU__1u8agt.jpg 480w, /_astro/the-end-of-css-day.D3S5yUU__n4uNO.jpg 800w, /_astro/the-end-of-css-day.D3S5yUU__Z1a1pjp.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="End of CSS day" loading="lazy" decoding="async" fetchpriority="auto" width="245" height="153"> </picture> <div class="embed"><iframe width="200" height="113" src="https://www.youtube.com/embed/lbItPyPQhAA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" title="CSS Day 2022 compilation" style=""></iframe></div>Brecht De RuyteThe details element, collapsing content without the hasslehttps://utilitybend.com/blog/the-details-element-collapsing-content-without-the-hassle/https://utilitybend.com/blog/the-details-element-collapsing-content-without-the-hassle/Some HTML5 elements seem to have the tendency of not being picked up by developers. Although widely supported, we still seem to use collapses with the use of a JS library instead of using a native element. In this little post, I want to highlight the details element a bit.Mon, 06 Jun 2022 00:00:00 GMT<picture> <source srcset="/_astro/details-visual.-E2y_Jg0_ZHegNs.avif 375w, /_astro/details-visual.-E2y_Jg0_1nxqDc.avif 480w, /_astro/details-visual.-E2y_Jg0_1nOicP.avif 680w, /_astro/details-visual.-E2y_Jg0_1mHxof.avif 800w, /_astro/details-visual.-E2y_Jg0_QcQ1A.avif 980w, /_astro/details-visual.-E2y_Jg0_1rb0vS.avif 1024w, /_astro/details-visual.-E2y_Jg0_1c6cPS.avif 1660w, /_astro/details-visual.-E2y_Jg0_Z29dAG3.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/details-visual.-E2y_Jg0_29OjjN.webp 375w, /_astro/details-visual.-E2y_Jg0_ZOA72t.webp 480w, /_astro/details-visual.-E2y_Jg0_ZOjfsP.webp 680w, /_astro/details-visual.-E2y_Jg0_ZPq0hq.webp 800w, /_astro/details-visual.-E2y_Jg0_Z1lUGE5.webp 980w, /_astro/details-visual.-E2y_Jg0_Z17ze0N.webp 1024w, /_astro/details-visual.-E2y_Jg0_Z1mE1FN.webp 1660w, /_astro/details-visual.-E2y_Jg0_ZrwFcr.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/details-visual.-E2y_Jg0_1M5ruE.jpg" srcset="/_astro/details-visual.-E2y_Jg0_2fTECp.jpg 375w, /_astro/details-visual.-E2y_Jg0_ZIuKIR.jpg 480w, /_astro/details-visual.-E2y_Jg0_ZIdTae.jpg 680w, /_astro/details-visual.-E2y_Jg0_ZJkDXO.jpg 800w, /_astro/details-visual.-E2y_Jg0_Z1fPllt.jpg 980w, /_astro/details-visual.-E2y_Jg0_1X8Ar0.jpg 1024w, /_astro/details-visual.-E2y_Jg0_1I3ML0.jpg 1660w, /_astro/details-visual.-E2y_Jg0_Z1a2LCi.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="accordion in html and css" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="support-of-the-details-element">Support of the details element.</h2> <p>Let’s start with the good news, apart from Internet Explorer and some older Edge browsers from pre-2020, the <a href="https://caniuse.com/?search=details" target="_blank" rel="noreferrer noopener">details element has great support</a>. For simple content it is accessible, has outstanding keyboard navigation and doesn’t require any JS to create a collapse. However, there seem to be some issues when adding interactive elements inside of it such as links or input elements. Read more about this in <a href="https://css-tricks.com/more-details-on-details/" target="_blank" rel="noreferrer noopener">the article of Geoff Graham</a></p> <h2 id="the-basic-html">The basic HTML</h2> <p>In case you didn’t know, the basic HTML for this is simple:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span>Here is a Title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> And here you can find the content <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <figure class="quarter image"><picture> <source srcset="/_astro/details-plain-html.DtPZbS8g_ZbABiA.avif 320w, /_astro/details-plain-html.DtPZbS8g_ZaLfhc.avif 480w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/details-plain-html.DtPZbS8g_1v5jb1.webp 320w, /_astro/details-plain-html.DtPZbS8g_1vTFcp.webp 480w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/details-plain-html.DtPZbS8g_Z2uJMjS.png" srcset="/_astro/details-plain-html.DtPZbS8g_ZOlag8.png 320w, /_astro/details-plain-html.DtPZbS8g_ZNvNeJ.png 480w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scrolling panels with tabs" loading="eager" decoding="async" fetchpriority="auto" width="818" height="530"> </picture></figure> <p>The <code>&lt;summary&gt;</code> element lets you give a title on which the extra information will be toggled.<br> On default, the details element is closed, but can be opened by adding the <code>open</code> attribute:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name">open</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span>Here is a Title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</span></span> And here you can find the content. I am open by default. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="styling-the-details-element">Styling the details element.</h2> <p>Let’s be honest, the default look &amp; feel of the details element looks a bit boring. Let’s create something very known and easy:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/dydKLLa?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token comment">&lt;!-- This is repeated 3 times in the example --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span> A little title or a question <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">&quot;</span>http://www.w3.org/2000/svg<span class="token punctuation">&quot;</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">&quot;</span>icon<span class="token punctuation">&quot;</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">&quot;</span>0 0 20 20<span class="token punctuation">&quot;</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>currentColor<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">fill-rule</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>evenodd<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z<span class="token punctuation">&quot;</span></span> <span class="token attr-name">clip-rule</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>evenodd<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</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">&quot;</span>details-info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Lorem ipsum dolor sit amet<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. Nam eget massa nec sem vulputate ullamcorper vel quis justo. Duis rhoncus tempor tempus. Nulla facilisi. Maecenas nulla ante, lacinia ac consectetur non, aliquet sollicitudin libero. Quisque congue odio sodales dui fermentum ac laoreet mauris eleifend. Nulla facilisi. Phasellus vel erat a ante pharetra pharetra.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>First we add some basic styling of the element.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--collapse-bg</span><span class="token punctuation">:</span> #e5fcff<span class="token punctuation">;</span> <span class="token property">--collapse-color</span><span class="token punctuation">:</span> #1a1a1a<span class="token punctuation">;</span> <span class="token property">--collapse-line-color</span><span class="token punctuation">:</span> #acacde<span class="token punctuation">;</span> <span class="token property">--collapse-icon-color</span><span class="token punctuation">:</span> #b8336a<span class="token punctuation">;</span> <span class="token property">--collapse-box-shadow</span><span class="token punctuation">:</span> <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.16<span class="token punctuation">)</span> 0px 10px 36px 0px<span class="token punctuation">,</span> <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.06<span class="token punctuation">)</span> 0px 0px 0px 1px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--collapse-bg<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>--collapse-color<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 property">margin-bottom</span><span class="token punctuation">:</span> 24px<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>--collapse-box-shadow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">summary, .details-info</span> <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 punctuation">}</span> <span class="token selector">.details-info</span> <span class="token punctuation">{</span> <span class="token property">border-top</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--collapse-line-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>You might’ve noticed that I wrapped the content inside of a <code>&lt;div&gt;</code>. This is purely for styling reasons. To play around with a border and padding. In the end, I decided to remove the default marker and replace it with my own custom toggle icon.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">summary</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> 1fr auto<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">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token property">list-style-type</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token comment">/* removes the default arrow */</span> <span class="token punctuation">}</span> <span class="token selector">summary .icon</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">width</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.26s<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>--collapse-icon-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details[open] summary .icon</span> <span class="token punctuation">{</span> <span class="token property">transform</span><span class="token punctuation">:</span> <span class="token function">rotate</span><span class="token punctuation">(</span>180deg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="so-why-is-it-that-we-still-tend-to-use-javascript-for-this">So why is it that we still tend to use JavaScript for this?</h2> <p>It’s mostly because of animating the collapse. While the default functionality is good, we like to animate things and that’s where a pure-css solution is hard to do. There are some workarounds to mimic that “bootstrappy collapse feel” but it’s not completely the same. There is a great article about this by Chris Coyier on <a href="https://css-tricks.com/how-to-animate-the-details-element/" target="_blank" rel="noreferrer noopener">how to animate the details element</a>.</p> <h2 id="creating-an-accordion-with-the-details-element">Creating an accordion with the details element.</h2> <p>Another thing that requires a bit of javascript is when you want to create an accordion. For example, when you have a group of FAQ items and only want one of them open. I’m not the biggest fan of accordions personally. But I created a <a href="https://codepen.io/utilitybend/pen/LYQBxpp" target="_blank" rel="noreferrer noopener">very simple example on how to create an accordion with the details element</a>.</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/LYQBxpp?default-tab=result" frameborder="0" allowtransparency="true" style=""></iframe></div> <p>The HTML of this is simple, I just grouped the elements by wrapping them, and as for the JS it’s as simple as removing the open attribute of the siblings when one of the details elements is clicked. I’m sure more advanced versions are possible, but it does the job.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>accordion-wrapper<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-accordion</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">&gt;</span></span> Some title <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">&quot;</span>http://www.w3.org/2000/svg<span class="token punctuation">&quot;</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">&quot;</span>icon<span class="token punctuation">&quot;</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">&quot;</span>0 0 20 20<span class="token punctuation">&quot;</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>currentColor<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">fill-rule</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>evenodd<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z<span class="token punctuation">&quot;</span></span> <span class="token attr-name">clip-rule</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>evenodd<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">&gt;</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">&quot;</span>details-info<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Some content <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- add more details elements below --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> accordionItems <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;[data-accordion] &gt; details&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">siblings</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>parentNode <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token class-name">Array</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>el<span class="token punctuation">.</span>parentNode<span class="token punctuation">.</span>children<span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">child</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> child <span class="token operator">!==</span> el<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> accordionItems<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> others <span class="token operator">=</span> <span class="token function">siblings</span><span class="token punctuation">(</span>el<span class="token punctuation">)</span><span class="token punctuation">;</span> others<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">sibling</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> sibling<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">&quot;open&quot;</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> <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> <p>And here is the CSS for that:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--collapse-bg</span><span class="token punctuation">:</span> #e6ebe0<span class="token punctuation">;</span> <span class="token property">--collapse-color</span><span class="token punctuation">:</span> #1a1a1a<span class="token punctuation">;</span> <span class="token property">--collapse-line-color</span><span class="token punctuation">:</span> #5ca4a9<span class="token punctuation">;</span> <span class="token property">--collapse-icon-color</span><span class="token punctuation">:</span> #ed6a5a<span class="token punctuation">;</span> <span class="token property">--collapse-box-shadow</span><span class="token punctuation">:</span> <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.16<span class="token punctuation">)</span> 0px 10px 36px 0px<span class="token punctuation">,</span> <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.06<span class="token punctuation">)</span> 0px 0px 0px 1px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.accordion-wrapper</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 property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--collapse-box-shadow<span class="token punctuation">)</span><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>--collapse-line-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--collapse-bg<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>--collapse-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details:not(:first-child)</span> <span class="token punctuation">{</span> <span class="token property">border-top</span><span class="token punctuation">:</span> 1px solid <span class="token function">var</span><span class="token punctuation">(</span>--collapse-line-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">summary</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> 1fr auto<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">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token property">list-style-type</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">summary, .details-info</span> <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 punctuation">}</span> <span class="token selector">.details-info</span> <span class="token punctuation">{</span> <span class="token property">border-top</span><span class="token punctuation">:</span> 1px dashed <span class="token function">var</span><span class="token punctuation">(</span>--collapse-line-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">summary .icon</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">width</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 0.26s<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>--collapse-icon-color<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">details[open] summary .icon</span> <span class="token punctuation">{</span> <span class="token property">transform</span><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 punctuation">}</span> </code></pre> <h3 id="now-lets-use-this-details-element-a-bit-more-shall-we">Now let’s use this details element a bit more shall we?</h3> <p>There are solutions for animating the collapse feel. It works out of the box and you don’t need to play around with aria attributes to “make it accessible”. Remember that every kilobyte you load on a website impacts the environment and your performance. So let’s at least think about using this instead of adding a complete library just to toggle some FAQ questions.</p>Brecht De RuyteA lot of power with little JavaScript, the HTML dialog element is herehttps://utilitybend.com/blog/a-lot-of-power-with-little-javascript-the-html-dialog-element-is-here/https://utilitybend.com/blog/a-lot-of-power-with-little-javascript-the-html-dialog-element-is-here/With Safari (15.4) being one of the last to implement the dialog element, a lot of browsers have great support for this element.. Goodbye to huge JavaScript libraries and welcome to the native HTML5 dialog element. This is beauty and simplicity on the web in its purest form. It's accessible, customisable and most of all: easy to use.Mon, 25 Apr 2022 00:00:00 GMT<picture> <source srcset="/_astro/dialog-visual.C5YvNz7g_2qzfbt.avif 375w, /_astro/dialog-visual.C5YvNz7g_Z21Nljk.avif 480w, /_astro/dialog-visual.C5YvNz7g_28l5UT.avif 680w, /_astro/dialog-visual.C5YvNz7g_Z1kPiFF.avif 800w, /_astro/dialog-visual.C5YvNz7g_Z1nR24m.avif 980w, /_astro/dialog-visual.C5YvNz7g_ZdHgae.avif 1024w, /_astro/dialog-visual.C5YvNz7g_ZSwsjn.avif 1660w, /_astro/dialog-visual.C5YvNz7g_SWx8.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/dialog-visual.C5YvNz7g_Z1eUhIY.webp 375w, /_astro/dialog-visual.C5YvNz7g_ZC6JpQ.webp 480w, /_astro/dialog-visual.C5YvNz7g_Z1x9qYy.webp 680w, /_astro/dialog-visual.C5YvNz7g_3QicN.webp 800w, /_astro/dialog-visual.C5YvNz7g_OyO7.webp 980w, /_astro/dialog-visual.C5YvNz7g_Z2qPNPT.webp 1024w, /_astro/dialog-visual.C5YvNz7g_1Xw7NS.webp 1660w, /_astro/dialog-visual.C5YvNz7g_2wkQOn.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/dialog-visual.C5YvNz7g_OrQ8E.jpg" srcset="/_astro/dialog-visual.C5YvNz7g_SlLDw.jpg 375w, /_astro/dialog-visual.C5YvNz7g_1vajWE.jpg 480w, /_astro/dialog-visual.C5YvNz7g_A7CnW.jpg 680w, /_astro/dialog-visual.C5YvNz7g_2c8mAj.jpg 800w, /_astro/dialog-visual.C5YvNz7g_296DcC.jpg 980w, /_astro/dialog-visual.C5YvNz7g_Z2kKsxi.jpg 1024w, /_astro/dialog-visual.C5YvNz7g_24Bt7u.jpg 1660w, /_astro/dialog-visual.C5YvNz7g_wQxsf.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Dialog visual styled with html and css" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="why-we-benefit-from-a-dialog-element-in-html5">Why we benefit from a dialog element in HTML5</h2> <p>Dialogs, modals, whatever you want to call them, they are everywhere. We use them for different reasons and they can become a hassle from time to time. When using third party libraries we turn to “css hacking” to get the look &amp; feel we want. We load tons of JavaScript into our platforms for even the smallest of dialog use cases and last but not least, a lot of times, the dialogs we implement aren’t even accessible. Let’s get a bit more into detail.</p> <h3 id="the-dialog-element-is-native">The<code> &lt;dialog&gt;</code> element is native</h3> <p>Every major browser has support for this element. They work the same way and have more or less the same user agent styling. There are times when we need support for older browsers or previous releases of the modern ones. When that’s the case we can still <a href="https://github.com/GoogleChrome/dialog-polyfill" target="_blank" rel="noreferrer noopener">rely on a polyfill to take care of this</a>. By adding the “open” attribute to the modal, we can define if it’s visible or not.</p> <h3 id="the-dialog-is-styleable">The <code>&lt;dialog&gt;</code> is styleable</h3> <p>Rounded corners, sticky headers, you name it. We can style everything in a dialog element and from what I read on the <a href="https://www.w3.org/TR/2013/CR-html5-20130806/interactive-elements.html#the-dialog-element" target="_blank" rel="noreferrer noopener">w3c interactive elements spec</a>: it supports <strong>flow content</strong>, this means that most elements that are used in the body of documents and applications can be used inside of it. This opens a lot of possibilities.</p> <h3 id="the-dialog-can-have-a-backdrop">The <code>&lt;dialog&gt;</code> can have a <code>::backdrop</code></h3> <p>If… you open it with a trigger in JavaScript, you can get a pseudo element ::backdrop which you can style. The backdrop is by default not clickable to close the dialog, however there are some ways that we can do that. I’ll give an example further in the article.</p> <h3 id="the-dialog-is-accessible">The <code>&lt;dialog&gt;</code> is accessible</h3> <p>It automatically shifts your next tabfocus inside the dialog element itself and in Chrome it seems to have some sort of focus trapping, at least in my Codepen examples. VoiceOver also makes it clear when you enter a dialog element.</p> <h3 id="the-dialog-only-needs-a-small-amount-of-javascript">The <code>&lt;dialog&gt;</code> only needs a small amount of JavaScript</h3> <p>Just some easy triggers that you need to call and you’re ready to go. Of Course you can expand on that by adding new click events on the backdrop and things like that. It’s a real front-end performance boost compared to the huge libraries we used to implement.</p> <h2 id="playing-around-with-the-dialog-element">Playing around with the dialog element.</h2> <p>I created the following codepen if you want to play around with it: <a href="https://codepen.io/utilitybend/pen/mdpRoZq" target="_blank" rel="noreferrer noopener">Dialog with sticky header in HTML</a><br> There are many ways to trigger a dialog but in its purest form, I created the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><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">&quot;</span>btn btn-primary<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-dialog<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-modeltrigger</span><span class="token punctuation">&gt;</span></span> Click me <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dialog</span> <span class="token attr-name">data-name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-dialog<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Well hello there!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</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">&quot;</span>btn btn-icon<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-dialog<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-modeltrigger</span><span class="token punctuation">&gt;</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">&quot;</span>http://www.w3.org/2000/svg<span class="token punctuation">&quot;</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>none<span class="token punctuation">&quot;</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">&quot;</span>0 0 24 24<span class="token punctuation">&quot;</span></span> <span class="token attr-name">stroke</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>currentColor<span class="token punctuation">&quot;</span></span> <span class="token attr-name">stroke-width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>2<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">stroke-linecap</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>round<span class="token punctuation">&quot;</span></span> <span class="token attr-name">stroke-linejoin</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>round<span class="token punctuation">&quot;</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>I am a lovely dialog with HTML. This is a really awesome HTML feature don&#39;t you think? Only requires a bit of javascript and so versatile.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>I even have a great scroll behaviour right outside the box :) check it out by editing this text<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>footer</span><span class="token punctuation">&gt;</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">&quot;</span>btn btn-primary-outline<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>my-dialog<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-modeltrigger</span><span class="token punctuation">&gt;</span></span> close <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>footer</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dialog</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>By adding some data attributes I created the following triggers in my Vanilla JS::</p> <ul> <li><code>data-modeltrigger</code> specifies that this element (button) triggers a modal</li> <li><code>data-target</code> gives a reference to the name of the dialog i want to trigger</li> <li><code>data-name</code> is added on the dialog which is referred to by the data target. (I chose not the use id’s)</li> </ul> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"> <span class="token keyword">const</span> triggers <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;[data-modeltrigger]&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> triggers<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> getTarget <span class="token operator">=</span> el<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">&quot;data-target&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> target <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[data-name=&quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>getTarget<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot;]</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>target<span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">&quot;open&quot;</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> target<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> target<span class="token punctuation">.</span><span class="token function">showModal</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><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>Inside the javascript I added a little check to see if the modal is already open or not, this way, creating buttons that open or close the modal are made easy.</p> <p>To wrap it up, I added a bit of custom styling to the modal as well, here is the main part of it:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Styling the modal and backdrop */</span> <span class="token selector">dialog</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">max-width</span><span class="token punctuation">:</span> 600px<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">background</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">border</span><span class="token punctuation">:</span> 1px solid #dedede<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>99<span class="token punctuation">,</span> 99<span class="token punctuation">,</span> 99<span class="token punctuation">,</span> 0.2<span class="token punctuation">)</span> 0px 2px 8px 0px<span class="token punctuation">;</span> <span class="token property">animation</span><span class="token punctuation">:</span> scale-in 0.4s ease-out<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">dialog::backdrop</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">radial-gradient</span><span class="token punctuation">(</span> circle<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>216<span class="token punctuation">,</span> 209<span class="token punctuation">,</span> 116<span class="token punctuation">,</span> 0.5<span class="token punctuation">)</span> 0%<span class="token punctuation">,</span> <span class="token function">rgba</span><span class="token punctuation">(</span>182<span class="token punctuation">,</span> 196<span class="token punctuation">,</span> 84<span class="token punctuation">,</span> 0.7<span class="token punctuation">)</span> 100% <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* bouncy animation because i can */</span> <span class="token atrule"><span class="token rule">@keyframes</span> scale-in</span> <span class="token punctuation">{</span> <span class="token selector">0%</span> <span class="token punctuation">{</span> <span class="token property">opacity</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">50%</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">scale</span><span class="token punctuation">(</span>0.9<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">100%</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> <span class="token punctuation">}</span> <span class="token comment">/* Styling inside of the modal in the demo */</span> </code></pre> <figure class=" image"><picture> <source srcset="/_astro/dialog-pop.srR4hT5L_Z2n9Qb2.avif 320w, /_astro/dialog-pop.srR4hT5L_QsMnm.avif 480w, /_astro/dialog-pop.srR4hT5L_1VCCQs.avif 800w, /_astro/dialog-pop.srR4hT5L_HUEzd.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/dialog-pop.srR4hT5L_Z24J4Ee.webp 320w, /_astro/dialog-pop.srR4hT5L_19SyTa.webp 480w, /_astro/dialog-pop.srR4hT5L_2f3png.webp 800w, /_astro/dialog-pop.srR4hT5L_11lr61.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/dialog-pop.srR4hT5L_Z2vkfzk.png" srcset="/_astro/dialog-pop.srR4hT5L_NN1Dg.png 320w, /_astro/dialog-pop.srR4hT5L_Z11KsBh.png 480w, /_astro/dialog-pop.srR4hT5L_3omQO.png 800w, /_astro/dialog-pop.srR4hT5L_Z1aiApq.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scrolling panels with tabs" loading="lazy" decoding="async" fetchpriority="auto" width="49" height="23"> </picture><figcaption>And so, a first <code>&lt;dialog&gt;</code> demo has been made.</figcaption></figure> <h3 id="clicking-the-backdrop-to-close-the-modal">Clicking the ::backdrop to close the modal</h3> <s>I haven’t really found a beautiful way to handle this. It seems that at the moment the easiest way to do this is to use the <code>getBoundingClientRect()</code> to check if a person is clicking outside of the modal and when doing so, trigger a <code>close()</code> on the dialog element.</s> <p><strong>Update:</strong> On LinkedIn, someone showed me a more elegant solution for closing the dialog based on the following tweet by <a href="https://twitter.com/argyleink/status/1511761383024521218" target="_blank" rel="noreferrer noopener">Adam Argyle</a>. This is much cleaner than calculating the mouse position.</p> <p>This is the JS I added (<em>updated</em>):</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"> <span class="token keyword">const</span> dialogs <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;dialog&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Check for click in backdrop */</span> dialogs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><span class="token literal-property property">target</span><span class="token operator">:</span>dialog<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>dialog<span class="token punctuation">.</span>nodeName <span class="token operator">===</span> <span class="token string">&#39;DIALOG&#39;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dialog<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token string">&#39;dismiss&#39;</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> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>See the codepen: <a href="https://codepen.io/utilitybend/pen/XWVxREm" target="_blank" rel="noreferrer noopener">Dialog with backdrop click close</a></p> <h3 id="taking-things-overboard-a-dialog-in-a-dialog-in-a-dialog">Taking things overboard: A dialog in a dialog in a dialog…</h3> <picture> <source srcset="/_astro/dialog-in-dialog.Cv1dYISe_2hmGMP.avif 320w, /_astro/dialog-in-dialog.Cv1dYISe_lrPKt.avif 480w, /_astro/dialog-in-dialog.Cv1dYISe_ZFNGUd.avif 800w, /_astro/dialog-in-dialog.Cv1dYISe_Z1qdReY.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/dialog-in-dialog.Cv1dYISe_Z168vwu.webp 320w, /_astro/dialog-in-dialog.Cv1dYISe_238Lf5.webp 480w, /_astro/dialog-in-dialog.Cv1dYISe_10Rdyo.webp 800w, /_astro/dialog-in-dialog.Cv1dYISe_gs3eC.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/dialog-in-dialog.Cv1dYISe_Z2tsfSq.png" srcset="/_astro/dialog-in-dialog.Cv1dYISe_1DC8Pi.png 320w, /_astro/dialog-in-dialog.Cv1dYISe_ZhhHc4.png 480w, /_astro/dialog-in-dialog.Cv1dYISe_Z1jyfRK.png 800w, /_astro/dialog-in-dialog.Cv1dYISe_Z23Xqcw.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scrolling panels with tabs" loading="lazy" decoding="async" fetchpriority="auto" width="70" height="33"> </picture> <p>If you read a few of my “trying out” posts before, you know that I like to play around with things, maybe break them, or just create something crazy. This is just me doing that. I created a little demo which nested the <a href="https://codepen.io/utilitybend/pen/ZEvqLwX" target="_blank" rel="noreferrer noopener">dialog elements inside of each other</a>, this should be valid, remember: <em>“flow content” is possible in dialog elements.</em></p> <p>In the end it’s just a bunch of dialogs triggering each other and a newly added <code>data-parenttrigger</code> attribute which force closes all the other dialogs on the page. By adding this little bit of JavaScript on it:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token comment">/* ... inside the trigger click event.. */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">&quot;data-parenttrigger&quot;</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> dialogs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> el<span class="token punctuation">.</span><span class="token function">close</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><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>See the demo <a href="https://codepen.io/utilitybend/pen/ZEvqLwX" target="_blank" rel="noreferrer noopener">A dialog inside a dialog inside a dialog</a> if you want a little laugh.</p> <p><strong>One important thing I noticed here</strong>: The backdrops of these dialogs seem to stack upon each other because they’re not pseudo elements to the dialog and not dependant on the page, so if you feel inspired by that, you might create something “<em>visually interesting</em>”.</p> <h2 id="final-monologue-on-the-dialog">Final <code>&lt;monologue&gt;</code> on the <code>&lt;dialog&gt;</code></h2> <p>Being a developer for quite some time, I had my fair share of dialogs. Hacking in JS and CSS to do something special with them that wasn’t supported by the library which provided them. Accessibility issues that just didn’t seem to find a right answer. I’m really happy with this addition and I hope it gets implemented by web developers around the world in the future. I’ve also been playing a lot of Elden Ring in my spare time, so “praise the message” (Elden Ring players will get this ;) )</p>Brecht De RuyteUsing best practices to create CSS scroll snapping tabshttps://utilitybend.com/blog/using-best-practices-to-create-css-scroll-snapping-tabs/https://utilitybend.com/blog/using-best-practices-to-create-css-scroll-snapping-tabs/Scroll snapping is hip and while we all have styled numerous sorts of tab panes and scroll boxes, I had an idea of combining them. What started out as a simple “scroll snapping experiment” turned out to be an accessibility study. How should scroll snapping tabs behave when using keys? By reading some best practices, I believe I found an elegant solution.Thu, 31 Mar 2022 00:00:00 GMT<picture> <source srcset="/_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z2juIQ5.avif 375w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_1DnYAP.avif 480w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_qWgAp.avif 680w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z2eqYM8.avif 800w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z19QP2U.avif 980w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z15HSj0.avif 1024w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z1yE3vJ.avif 1660w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z1KjjYA.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual-scroll-snapping-tabs.Cf6B9LpB_f85XH.webp 375w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_ZRajnj.webp 480w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z24B2nJ.webp 680w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_kbP2E.webp 800w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_1oKYLR.webp 980w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_2e1Ldn.webp 1024w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_1K5B0D.webp 1660w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Zq5BTA.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual-scroll-snapping-tabs.Cf6B9LpB_2vHa2T.jpg" srcset="/_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Zt3TMy.jpg 375w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z1Amk9z.jpg 480w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_2ho5DV.jpg 680w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Zo0aIB.jpg 800w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_FyY0B.jpg 980w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_6HWsY.jpg 1024w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_ZmdcIK.jpg 1660w, /_astro/visual-scroll-snapping-tabs.Cf6B9LpB_Z241kD.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Snapping finger with tabs" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="the-basic-scroll-snapping-idea-with-css">The basic scroll snapping idea with CSS.</h2> <p>It started out as a simple test. By having a container with 2 lists. The lists would be next to each other and by scrolling left and right, you could toggle between them.</p> <p>The <strong>HTML</strong> setup was very simple:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-snap-panel<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-container container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-list people<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- some list items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-list groups<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- some list items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The <code>.scroll-container</code> would become a grid with 2 items that have the full width of their container. The scroll container would have an <code>overflow-x</code> and some smooth scroll snapping options.</p> <p>Here is the basic <strong>CSS</strong> for this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">:root</span> <span class="token punctuation">{</span> <span class="token property">--container-width</span><span class="token punctuation">:</span> 480px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.container</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">.scroll-snap-panel</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">margin</span><span class="token punctuation">:</span> 30px auto<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> <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.3<span class="token punctuation">)</span> 0px 10px 50px<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--container-width<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* the scroll magic */</span> <span class="token selector">.scroll-container</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">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> <span class="token function">var</span><span class="token punctuation">(</span>--container-width<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">-webkit-overflow-scrolling</span><span class="token punctuation">:</span> touch<span class="token punctuation">;</span> <span class="token property">overflow-x</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> x mandatory<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 property">max-height</span><span class="token punctuation">:</span> 85vh<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid #CCC<span class="token punctuation">;</span> <span class="token property">border-top</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.scroll-list</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 property">height</span><span class="token punctuation">:</span> 80vh<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">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token property">scroll-snap-type</span><span class="token punctuation">:</span> y proximity<span class="token punctuation">;</span> <span class="token property">overflow-y</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.scroll-list 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> 80px 1fr<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 20px<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">padding</span><span class="token punctuation">:</span> 15px 20px<span class="token punctuation">;</span> <span class="token property">border-bottom</span><span class="token punctuation">:</span> 1px solid #aaa<span class="token punctuation">;</span> <span class="token property">scroll-snap-align</span><span class="token punctuation">:</span> start<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <figure class=" image"><picture> <source srcset="/_astro/scroll-snapping-no-tabs.DDfggLD5_ZhoIU.avif 320w, /_astro/scroll-snapping-no-tabs.DDfggLD5_P86W5.avif 480w, /_astro/scroll-snapping-no-tabs.DDfggLD5_Z1S4Y5r.avif 800w, /_astro/scroll-snapping-no-tabs.DDfggLD5_1bRKRb.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/scroll-snapping-no-tabs.DDfggLD5_Z1CcXCr.webp 320w, /_astro/scroll-snapping-no-tabs.DDfggLD5_ZLMrVr.webp 480w, /_astro/scroll-snapping-no-tabs.DDfggLD5_1zbzOX.webp 800w, /_astro/scroll-snapping-no-tabs.DDfggLD5_Zq2N1l.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/scroll-snapping-no-tabs.DDfggLD5_Z2kEuN4.png" srcset="/_astro/scroll-snapping-no-tabs.DDfggLD5_11lPSO.png 320w, /_astro/scroll-snapping-no-tabs.DDfggLD5_1QLmzO.png 480w, /_astro/scroll-snapping-no-tabs.DDfggLD5_ZQqIrH.png 800w, /_astro/scroll-snapping-no-tabs.DDfggLD5_2dw1uU.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Panels with no tabs" loading="lazy" decoding="async" fetchpriority="auto" width="450" height="190"> </picture><figcaption>The first example</figcaption></figure> <p>(<a href="https://codepen.io/utilitybend/pen/JjMrjea)" target="_blank" rel="noreferrer noopener">codepen for the first step here</a></p> <h2 id="a-fun-little-component-but-not-accessible-at-all">A fun little component, but not accessible at all.</h2> <p>There are a two main problems with this little component:</p> <ul> <li>It can only be navigated by using a mouse / trackpad</li> <li>There is no indication that it can be scrolled horizontally if you don’t have your scrollbars visible at all times.</li> </ul> <p>In short, not accessible at all and it got me thinking. What if I made a hybrid component where you can use this awesome scroll snapping functionality in combination with tabs. This would:</p> <ul> <li>Create a fun mobile experience</li> <li>Still have the functionality of tabs</li> <li>Would fix some accessibility issues along the way</li> </ul> <h3 id="first-things-first-fixing-the-ux">First things first, fixing the UX.</h3> <p>(<a href="https://codepen.io/utilitybend/full/gOoGbMr)" target="_blank" rel="noreferrer noopener">Visual representation of this step</a></p> <p>The first thing on the agenda was making an indication of the hidden content simply by adding some tabs to our HTML:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-snap-panel<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>tabs-container container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>people-tab<span class="token punctuation">&quot;</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">&quot;</span>people tab active<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>People<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>groups-tab<span class="token punctuation">&quot;</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">&quot;</span>groups tab<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Groups<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>scroll-container container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>scroll-list people<span class="token punctuation">&quot;</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">&quot;</span>people-tab<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- List items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</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">&quot;</span>scroll-list groups<span class="token punctuation">&quot;</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">&quot;</span>groups-tab<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- List items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>The next thing I did was adding a bit of CSS for the active tabs and adding some JS, which will use the tabs to scroll to the right position. You can view the CSS in the demo, but the basic idea of the JS was the following:</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> tabs <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;.tab&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> scrollContainer <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">&quot;.scroll-container&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> scrollLists <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">&quot;.scroll-list&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* check for clicks and update scroll position based on target offset */</span> tabs<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tab</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> tab<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> target <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>tab<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>target<span class="token punctuation">)</span><span class="token punctuation">.</span>offsetLeft<span class="token punctuation">;</span> scrollContainer<span class="token punctuation">.</span>scrollLeft <span class="token operator">=</span> target<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><span class="token punctuation">;</span> <span class="token comment">/* when the scrollcontainer is scrolled horizontally, check the current scrollposition is the same as the left offset, and set the tab active when that&#39;s the case */</span> scrollContainer<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">&quot;scroll&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> scrollPos <span class="token operator">=</span> scrollContainer<span class="token punctuation">.</span>scrollLeft<span class="token punctuation">;</span> scrollLists<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">list</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> listId <span class="token operator">=</span> list<span class="token punctuation">.</span>id<span class="token punctuation">;</span> <span class="token keyword">let</span> listButton <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">.tab[data-target=&quot;</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>listId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&quot;]</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>scrollPos <span class="token operator">===</span> list<span class="token punctuation">.</span>offsetLeft<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token operator">...</span>listButton<span class="token punctuation">.</span>parentElement<span class="token punctuation">.</span>children<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">sib</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> sib<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">&quot;active&quot;</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> listButton<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">&quot;active&quot;</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><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>And for a moment, I thought it was perfect:</p> <ul> <li>It has an indication of multiple panes</li> <li>You can use tab and shift-tab to switch between tabs and enter to activate them.</li> </ul> <p>But I still wasn’t happy. There was no indication of tabs when using voice over, The list is not vertically scrollable with a keyboard. So when it came to accessibility. I still had ways to go.</p> <h2 id="the-final-improvement-to-make-it-accessible">The final improvement to make it accessible.</h2> <p>(<a href="https://codepen.io/utilitybend/pen/poWWaRv)" target="_blank" rel="noreferrer noopener">Visual representation of this step</a></p> <p>I knew I had some reading to do. So I started looking at <a href="https://www.w3.org/TR/wai-aria-practices/#tabpanel" target="_blank" rel="noreferrer noopener">the documentation of tabs at the aria working group website</a>. A quick summary of how tabs should behave:</p> <p><strong>Using TAB</strong></p> <ul> <li>The first TAB should enter the tab component on the first (active) button.</li> <li>The second TAB should change the focus inside the active tabpanel.</li> <li>The third TAB should leave the tabbed component</li> </ul> <p><strong>Arrow keys</strong></p> <ul> <li>While focussing on the tab button, the arrows should switch between them.</li> <li>The tabpanels can either be activated automatically while using the arrow or manually by pressing the enter key (for this example, I chose automatically)</li> </ul> <p><strong>Special case</strong></p> <p>Because I’m working with scroll behaviour, I wanted the arrow keys to work inside the tabpanel itself. By pressing the left or right arrow key while scrolling through the list, I want it to switch the active panel and focus on the new active tab as an indication of what happened.</p> <p>They have <a href="https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html" target="_blank" rel="noreferrer noopener">some great examples</a> at the ARIA working group, so based on their example of automated tabs, I started by changing my HTML to the following:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>scroll-snap-panel<span class="token punctuation">&quot;</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">&quot;</span>tablist<span class="token punctuation">&quot;</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">&quot;</span>users and groups<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>tabs-container container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>people-tab<span class="token punctuation">&quot;</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">&quot;</span>tab<span class="token punctuation">&quot;</span></span> <span class="token attr-name">aria-selected</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>true<span class="token punctuation">&quot;</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">&quot;</span>people tab active<span class="token punctuation">&quot;</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">&quot;</span>people-tab<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>People<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">data-target</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>groups-tab<span class="token punctuation">&quot;</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">&quot;</span>tab<span class="token punctuation">&quot;</span></span> <span class="token attr-name">aria-selected</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>false<span class="token punctuation">&quot;</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">&quot;</span>groups tab<span class="token punctuation">&quot;</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">&quot;</span>groups-tab<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-1<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Groups<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>scroll-container container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>scroll-list people<span class="token punctuation">&quot;</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">&quot;</span>tabpanel<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>0<span class="token punctuation">&quot;</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">&quot;</span>people-tab<span class="token punctuation">&quot;</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">&quot;</span>people<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- list items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>scroll-list groups<span class="token punctuation">&quot;</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">&quot;</span>tabpanel<span class="token punctuation">&quot;</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>-1<span class="token punctuation">&quot;</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">&quot;</span>groups-tab<span class="token punctuation">&quot;</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">&quot;</span>groups<span class="token punctuation">&quot;</span></span> <span class="token attr-name">hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>hidden<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- list items here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Something to note here. You might notice that I wrapped my lists inside another <code>&lt;div&gt;</code>. This is because a <code>role=”tabpanel”</code> should not be used on an unordered list.</p> <p>By adding <a href="https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html" target="_blank" rel="noreferrer noopener">a bit of JS from the ARIA working group</a> and infusing it with my own code. I was able to create something interesting and accessible. I learned a lot from this experience and the goal of this post was to take you on a little thought journey. Accessibility is very important, and we might not get it perfect all the time, but a bit of effort can go a long way.</p> <p><a href="https://codepen.io/utilitybend/pen/poWWaRv" target="_blank" rel="noreferrer noopener">The final example</a>, with kudos to people who create examples in the ARIA working group.</p> <figure class=" image"><picture> <source srcset="/_astro/scroll-snapping-tabs.CWp5bkf1_Z2ugAPc.avif 320w, /_astro/scroll-snapping-tabs.CWp5bkf1_Z7PaUO.avif 480w, /_astro/scroll-snapping-tabs.CWp5bkf1_1Pgfxb.avif 800w, /_astro/scroll-snapping-tabs.CWp5bkf1_Z2oc1Nn.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/scroll-snapping-tabs.CWp5bkf1_CsJxD.webp 320w, /_astro/scroll-snapping-tabs.CWp5bkf1_Z25hXlU.webp 480w, /_astro/scroll-snapping-tabs.CWp5bkf1_Z7bwRU.webp 800w, /_astro/scroll-snapping-tabs.CWp5bkf1_Ixjzs.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/scroll-snapping-tabs.CWp5bkf1_Z1Ikg9T.png" srcset="/_astro/scroll-snapping-tabs.CWp5bkf1_ZsbRbj.png 320w, /_astro/scroll-snapping-tabs.CWp5bkf1_1TexI4.png 480w, /_astro/scroll-snapping-tabs.CWp5bkf1_Z1cQ9BR.png 800w, /_astro/scroll-snapping-tabs.CWp5bkf1_Zm7i9u.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Scrolling panels with tabs" loading="lazy" decoding="async" fetchpriority="auto" width="360" height="170"> </picture><figcaption>The final example</figcaption></figure>Brecht De RuyteA love letter to the CSS-developerhttps://utilitybend.com/blog/a-love-letter-to-the-css-developer/https://utilitybend.com/blog/a-love-letter-to-the-css-developer/It's valentine's day and so the perfect time to write a love letter. This one is to all the CSS-developers out there. For those who love the cascade, pseudo elements and all the new nifty things entering the language.Mon, 14 Feb 2022 00:00:00 GMT<picture> <source srcset="/_astro/love-css.CdoEgd6C_Z1OUPmF.avif 375w, /_astro/love-css.CdoEgd6C_1EtCcj.avif 480w, /_astro/love-css.CdoEgd6C_Z7K0vM.avif 680w, /_astro/love-css.CdoEgd6C_Z1xHTx4.avif 800w, /_astro/love-css.CdoEgd6C_ZR9DsJ.avif 980w, /_astro/love-css.CdoEgd6C_Zff9nP.avif 1024w, /_astro/love-css.CdoEgd6C_2p34XG.avif 1660w, /_astro/love-css.CdoEgd6C_Z1OSL33.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/love-css.CdoEgd6C_ZvwPKL.webp 375w, /_astro/love-css.CdoEgd6C_Z26jw0I.webp 480w, /_astro/love-css.CdoEgd6C_1bCY57.webp 680w, /_astro/love-css.CdoEgd6C_ZejTVa.webp 800w, /_astro/love-css.CdoEgd6C_rel8a.webp 980w, /_astro/love-css.CdoEgd6C_Z4Dvbm.webp 1024w, /_astro/love-css.CdoEgd6C_Z2uxpCL.webp 1660w, /_astro/love-css.CdoEgd6C_Z1wsYwf.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/love-css.CdoEgd6C_ZEaWaQ.jpg" srcset="/_astro/love-css.CdoEgd6C_Z22ARG5.jpg 375w, /_astro/love-css.CdoEgd6C_1rNzRT.jpg 480w, /_astro/love-css.CdoEgd6C_Zkq2Pc.jpg 680w, /_astro/love-css.CdoEgd6C_Z1KnVQt.jpg 800w, /_astro/love-css.CdoEgd6C_Z14OFM9.jpg 980w, /_astro/love-css.CdoEgd6C_Z1ATbqB.jpg 1024w, /_astro/love-css.CdoEgd6C_13o2UU.jpg 1660w, /_astro/love-css.CdoEgd6C_1UxtTt.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A heart between brackets" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><p>My dearest CSS-developer, o how the times are a-changin’. You’re probably writing more JavaScript in your life, more than you’d want to, you might even have picked up some frameworks such as React or Vue. How can you keep your identity as a CSS-developer, how can you keep expressing your love for that perfect layout in the language you’ve mastered so well…</p> <h2 id="its-not-the-first-time-that-jobs-get-mixed-up">It’s not the first time that jobs get mixed up</h2> <p>You might have started out as a designer, expressing your artistic skill to create beautiful websites on a 960px grid in Photoshop and later on learning to code a bit of HTML and CSS, using the power of floats, it was a skill only a few could master.</p> <p>At a certain point, you chose to give up the designing aspect and started to just code other people’s artwork into interactive elements. You did this pixel-perfect and even threw in some JavaScript in the form of jQuery into the mix, making you that person who was awesome. Back-end developers loved how their database-scheme was shown so beautifully on a website and designers high-fived you because their creativity just turned into a masterpiece.</p> <h2 id="so-then-you-became-a-front-end-developer">So then you became a front-end developer</h2> <p>Times are changing again, you learned a bit of PHP and could use different templating languages such as Twig, you could perfectly create your own Wordpess blog and play around in so many environments, you could create a beautiful website on almost every platform out there. But even that was not enough, there was something on the rise, the wonderful world of JavaScript. And before you knew it, you were starting to write a bit of React or a bit of Vue, you were creating Webpacks after already mastering a bit of Gulp or even Grunt. You were already writing CSS in a SASS or Less environment to reduce time, you might even be using Bootstrap because it just made everything go smooth and efficient.</p> <h2 id="dont-forget-about-accessibility-and-technical-seo">Don’t forget about accessibility and technical SEO</h2> <p>A lot of times people seem to forget that you are the first technical person in taking the first step to creating something accessible that is perfectly readable for search engines. You take the first steps into technical SEO by adding microdata and using correct semantics to please all users and robots alike. A lot of times, you are the first one to spot inconsistencies and you have no problem reporting something user-unfriendly when you are creating your templates.</p> <h2 id="but-now-you-are-starting-to-feel-a-loss-and-i-hear-you">But now you are starting to feel a loss, and I hear you…</h2> <p>You have learned to be efficient, you make websites at the speed of light, but you get the feeling that a lot of things are starting to feel the same. Using the same visual styles over and over again because they are “like that in the framework”. You miss using a part of the skill you’ve mastered before, creating something amazing with just the power of CSS and great semantics. I’m telling you, you are not alone and many front-end, or should I say CSS-developers, feel that way. And I do believe that it’s time that the term front-end development shouldn’t be so generalised as it is now. But I’m telling you, the future is bright because there is a shift in the web, something that had to happen a long time ago.</p> <h3 id="the-clients-want-more-creativity-and-visual-identity">The clients want more creativity and visual identity</h3> <p>Especially with the pandemic, a lot of webshops have sprouted and now we have a lot of websites that kinda look the same, and a lot of brands are starting to notice that as well. They want something to remember, they want something visually different while still maintaining accessibility, great UX and search-engine optimisation. You have the power to do that, with the more evolving CSS language and browsers that update automatically, you have a toolset for this, a toolset you love and master.</p> <h3 id="javascript-includes-more-and-more-back-end-work">JavaScript includes more and more “back-end” work</h3> <p>A lot of the projects you encounter are getting really technical and there are a lot of JavaScript developers out there. Those people are great and a lot of them don’t have those visual skills that you’ve mastered, they will help you out, and you can work together as a team. They will be happy to see their JavaScript code on such a beautiful layout and you will be happy to see everything work so snappy and strong.</p> <h2 id="lets-call-you-the-client-side-developer">Let’s call you the client-side developer</h2> <p>Because in the end, that is where you shine, creating something visually pleasing, responsive, accessible. Something with powerful semantics that is scalable, expendable, lightweight and performant. By adding a bit of client-side JS you can do wonders that please the eye. You integrate animation where needed and tweak all the things until they are picture perfect. Believe in your craft and never forget that even in “just writing CSS” you can grow, explore and have a lot of fun. Take it from me, even though I write a bit of React, know a bit of PHP, the heart is where the CSS lies and that’s perfect. Follow your passion and become a master of all things visual and client-side.</p> <blockquote> <p>You are the one, who gets the layout made<br> The one who loves perfect semantics<br> and mastered the cascade<br> You know how a design should flow<br> Making all things interactive<br> Creating projects, with room to grow<br> You are a master when it comes to CSS<br> The one who loves the web<br> and makes sure everyone has access<br> You translate the design, it’s where you prosper<br> Other skills might be bonus<br> But you are, by far, my favourite developer.</p> </blockquote>Brecht De RuyteFun things you can do with CSS Grid besides your basic layouthttps://utilitybend.com/blog/fun-things-you-can-do-with-css-grid-besides-your-basic-layout/https://utilitybend.com/blog/fun-things-you-can-do-with-css-grid-besides-your-basic-layout/CSS grid is a lot of fun and I find myself using it far more often than I used to. Goodbye to the Flexbox grid and hello to all things grid. Here are some of the fun things you can do with the CSS grid module.Sat, 22 Jan 2022 00:00:00 GMT<picture> <source srcset="/_astro/visual.CGmcuoCJ_ZgGb4K.avif 375w, /_astro/visual.CGmcuoCJ_Z1yj8n2.avif 480w, /_astro/visual.CGmcuoCJ_25Qq7a.avif 680w, /_astro/visual.CGmcuoCJ_Z1eoWiY.avif 800w, /_astro/visual.CGmcuoCJ_Z23wONI.avif 980w, /_astro/visual.CGmcuoCJ_ZR6X2X.avif 1024w, /_astro/visual.CGmcuoCJ_ZIxB2X.avif 1660w, /_astro/visual.CGmcuoCJ_Z1PNJzv.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/visual.CGmcuoCJ_20vktN.webp 375w, /_astro/visual.CGmcuoCJ_HSnbw.webp 480w, /_astro/visual.CGmcuoCJ_ZH8c8d.webp 680w, /_astro/visual.CGmcuoCJ_12Myfz.webp 800w, /_astro/visual.CGmcuoCJ_dEFJP.webp 980w, /_astro/visual.CGmcuoCJ_Z1jwVVL.webp 1024w, /_astro/visual.CGmcuoCJ_Z1aXzVL.webp 1660w, /_astro/visual.CGmcuoCJ_ZwpJXB.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/visual.CGmcuoCJ_ZbNo0k.jpg" srcset="/_astro/visual.CGmcuoCJ_N8LU6.jpg 375w, /_astro/visual.CGmcuoCJ_Zttanb.jpg 480w, /_astro/visual.CGmcuoCJ_Z1TuJGU.jpg 680w, /_astro/visual.CGmcuoCJ_Z9yYj8.jpg 800w, /_astro/visual.CGmcuoCJ_ZXGQNR.jpg 980w, /_astro/visual.CGmcuoCJ_24A8WQ.jpg 1024w, /_astro/visual.CGmcuoCJ_2d9uWQ.jpg 1660w, /_astro/visual.CGmcuoCJ_Z23tLSU.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A little manga face drawn with CSS" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="first-things-first-the-problem-with-the-css-grid-module">First things first: The problem with the CSS Grid module</h2> <p>Using CSS grid is a blast, and I have been playing around with it from time to time, but for some reason, I never found the time to actually take a deep dive in it and it never felt like a natural thing to use. The main problem with it is that it never got the hype that Flexbox had a few years back. People were able to use grids, Bootstrap and many other libraries had a brilliant integration of a Flex grid and so, a lot of developers just didn’t find the need for it.</p> <p>I am one of those developers, the syntax seemed a bit hard, I didn’t find a reason to make the switch. But from the start of 2021 till now, I started doing a bit of effort to use it from time to time and suddenly, it just clicked. I noticed my code getting shorter, my HTML more read-able and before i knew it, I was hooked. So now I’m using the CSS grid layout for more things and here are some of the fun ones I played around with. After all, sharing is caring.</p> <h2 id="buttons-with-css-grid">Buttons with CSS Grid</h2> <picture> <source srcset="/_astro/css-grid-buttons.CzAtQ6Dw_ZS3emM.avif 320w, /_astro/css-grid-buttons.CzAtQ6Dw_Z11yOAO.avif 480w, /_astro/css-grid-buttons.CzAtQ6Dw_1HsJ0P.avif 800w, /_astro/css-grid-buttons.CzAtQ6Dw_Z28dHCQ.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/css-grid-buttons.CzAtQ6Dw_NCG6O.webp 320w, /_astro/css-grid-buttons.CzAtQ6Dw_F75RM.webp 480w, /_astro/css-grid-buttons.CzAtQ6Dw_Z1F2tju.webp 800w, /_astro/css-grid-buttons.CzAtQ6Dw_ZqwM9f.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/css-grid-buttons.CzAtQ6Dw_Z1E5r0X.png" srcset="/_astro/css-grid-buttons.CzAtQ6Dw_Z1vMMkk.png 320w, /_astro/css-grid-buttons.CzAtQ6Dw_Z1Ejnym.png 480w, /_astro/css-grid-buttons.CzAtQ6Dw_14Ib3i.png 800w, /_astro/css-grid-buttons.CzAtQ6Dw_2jdRdx.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="buttons with a hole on each side" loading="lazy" decoding="async" fetchpriority="auto" width="360" height="161"> </picture> <p>I created a few buttons with the module. The idea was to keep my HTML as short as possible. By using pseudo elements, we can actually make them a part of the grid and style them. So here is the HTML we start with.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>This is my button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Very simple indeed, what we need next is a bit of styling of our button. So here the trickery starts, let’s make our button a grid and add some basic styling to it.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">button</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> 1fr auto 1fr<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">padding</span><span class="token punctuation">:</span> 5px<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">&#39;Lato&#39;</span><span class="token punctuation">,</span> sans-serif<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-weight</span><span class="token punctuation">:</span> 600<span class="token punctuation">;</span> <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<span class="token punctuation">;</span> <span class="token property">letter-spacing</span><span class="token punctuation">:</span> .08em<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 property">color</span><span class="token punctuation">:</span> #FEFEFE<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> #C200FB<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 30px<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 punctuation">}</span> </code></pre> <p>We now have a button that is a three column grid, and the center column has an auto width. We can now start adding our pseudo elements and create those “punch out holes” or dots.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* style the button to have holes in it, the pseudo element is a part of the grid */</span> <span class="token selector">button::before, button::after</span><span class="token punctuation">{</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&#39;&#39;</span><span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0 10px<span class="token punctuation">;</span> <span class="token property">box-shadow</span><span class="token punctuation">:</span> inset <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.2<span class="token punctuation">)</span> 0px 0px 3px<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">box-shadow</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--container-background<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* to make sure the right &quot;column&quot; stays aligned to the end */</span> <span class="token selector">button::after</span> <span class="token punctuation">{</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>And there you have it, a very simple button that looks like it has a lot more HTML going on, but in fact, it’s so clean and simple. You might have noticed that in the teaser image above, the punch out holes have the same color as the background. This is by using a little CSS variable trick based on the container. By adding a variable color to the container background and using that same variable on the pseudo element’s background.</p> <a href="https://codepen.io/utilitybend/pen/YzrMoRE?editors=1100" target="_blank" rel="noreferrer noopener">You can see the full example on codepen.</a> <h2 id="styling-a-title-with-lines-on-both-sides">Styling a title with lines on both sides</h2> <picture> <source srcset="/_astro/css-grid-titles.DOsf_QXP_1JpWw6.avif 320w, /_astro/css-grid-titles.DOsf_QXP_1tgAsn.avif 480w, /_astro/css-grid-titles.DOsf_QXP_mAciY.avif 800w, /_astro/css-grid-titles.DOsf_QXP_Z1M3Pwh.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/css-grid-titles.DOsf_QXP_ZOkh0A.webp 320w, /_astro/css-grid-titles.DOsf_QXP_Z15tD4j.webp 480w, /_astro/css-grid-titles.DOsf_QXP_Z2ca2dH.webp 800w, /_astro/css-grid-titles.DOsf_QXP_In3JX.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/css-grid-titles.DOsf_QXP_cOo23.png" srcset="/_astro/css-grid-titles.DOsf_QXP_Z22XhXX.png 320w, /_astro/css-grid-titles.DOsf_QXP_Z2j7E2G.png 480w, /_astro/css-grid-titles.DOsf_QXP_1Eo5BQ.png 800w, /_astro/css-grid-titles.DOsf_QXP_ZufWdp.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Titles with a seperating lines" loading="lazy" decoding="async" fetchpriority="auto" width="490" height="171"> </picture> <p>Chances are big that during your career you had to style a title that kind of looks like the one above. You might remember having a hard time with this, maybe writing some extra spans or other tags just to get the desired result. The technique used for this, is pretty much the same one as the button example but now we just keep it simple with a simple <code>&lt;h1&gt;</code> tag:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">&gt;</span></span>My super awesome title<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And once again, we use our grid and pseudo elements to create the lines and the column gap for the spacing.</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">h1</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> 1fr auto 1fr<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">column-gap</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 10px 0<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">&#39;Merriweather&#39;</span><span class="token punctuation">,</span> serif<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 2.2rem<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span> <span class="token property">line-height</span><span class="token punctuation">:</span> 1.2<span class="token punctuation">;</span> <span class="token property">letter-spacing</span><span class="token punctuation">:</span> .05em<span class="token punctuation">;</span> <span class="token property">text-transform</span><span class="token punctuation">:</span> uppercase<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">h1::before, h1::after</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 1px<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> #000<span class="token punctuation">;</span> <span class="token property">content</span><span class="token punctuation">:</span> <span class="token string">&#39;&#39;</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This way of working really helps the workflow and keeps your HTML nice and clean. You can also use grid rows to create other effects. <a href="https://codepen.io/utilitybend/pen/VwMdRQm" target="_blank" rel="noreferrer noopener">Click here to see the other examples.</a></p> <h2 id="using-grid-to-make-a-list-span-over-multiple-columns">Using Grid to make a list span over multiple columns</h2> <picture> <source srcset="/_astro/css-grid-lists.ZfV4YAQA_gkXid.avif 320w, /_astro/css-grid-lists.ZfV4YAQA_Z16Rj5c.avif 480w, /_astro/css-grid-lists.ZfV4YAQA_Z1cGgEV.avif 800w, /_astro/css-grid-lists.ZfV4YAQA_Z1cD5Fl.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/css-grid-lists.ZfV4YAQA_Z1VMzns.webp 320w, /_astro/css-grid-lists.ZfV4YAQA_1Kbh34.webp 480w, /_astro/css-grid-lists.ZfV4YAQA_1Emjsk.webp 800w, /_astro/css-grid-lists.ZfV4YAQA_1EpurU.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/css-grid-lists.ZfV4YAQA_2fog1u.png" srcset="/_astro/css-grid-lists.ZfV4YAQA_1LVy5L.png 320w, /_astro/css-grid-lists.ZfV4YAQA_oIgHm.png 480w, /_astro/css-grid-lists.ZfV4YAQA_iTj7C.png 800w, /_astro/css-grid-lists.ZfV4YAQA_iWu7d.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="List spanned over multiple columns" loading="lazy" decoding="async" fetchpriority="auto" width="720" height="311"> </picture> <p>This is a neat little trick I found out recently. When you want a list (<code>&lt;ul&gt;</code> or <code>&lt;ol&gt;</code>) to jump to another column after a few items, you can use the <code>grid-auto-flow</code> property to make this dynamic. It’s not a 100% failsafe when content is being entered by a CMS because it will keep creating columns until infinity. However, if you have a little bit of control, this might be the perfect solution for you.</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>list<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>If you&#39;re not hurting you&#39;re not winning<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Swim with sharks<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Thinking outside the box<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Really threw me under the bus<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Let&#39;s not solutionize this<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>That&#39;s mint<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>We don&#39;t need to boil the ocean here<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- add a bunch more here --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" 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-template-rows</span><span class="token punctuation">:</span> <span class="token function">repeat</span><span class="token punctuation">(</span>5<span class="token punctuation">,</span> auto<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">grid-gap</span><span class="token punctuation">:</span> 30px<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 punctuation">}</span> </code></pre> <p>By using a combination of rows and auto-flow, we created something that would’ve cost us a lot more hassle in the past. This of course is just the basics of this integration, <a href="https://codepen.io/utilitybend/pen/VwMNpQq?editors=1100" target="_blank" rel="noreferrer noopener">the fully styled example can be found on my codepen.</a></p> <h2 id="angry-manga-face">Angry manga face</h2> <picture> <source srcset="/_astro/css-grid-manga.Cg77lQV2_Z1RW1MU.avif 320w, /_astro/css-grid-manga.Cg77lQV2_f7brF.avif 480w, /_astro/css-grid-manga.Cg77lQV2_1sqqmf.avif 800w, /_astro/css-grid-manga.Cg77lQV2_22GnhS.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/css-grid-manga.Cg77lQV2_Y6ykl.webp 320w, /_astro/css-grid-manga.Cg77lQV2_Z1X1me0.webp 480w, /_astro/css-grid-manga.Cg77lQV2_ZJH7jq.webp 800w, /_astro/css-grid-manga.Cg77lQV2_ZaranM.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/css-grid-manga.Cg77lQV2_pVhWO.png" srcset="/_astro/css-grid-manga.Cg77lQV2_Zmlr0m.png 320w, /_astro/css-grid-manga.Cg77lQV2_1KHLfe.png 480w, /_astro/css-grid-manga.Cg77lQV2_Z26a7E8.png 800w, /_astro/css-grid-manga.Cg77lQV2_Z1vTaIu.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Angry manga face" loading="lazy" decoding="async" fetchpriority="auto" width="35" height="16"> </picture> <p>I won’t place the css of this on the blog because truth be told, it’s a little bit messy. I don’t have any awesome drawing skills but I always wanted to “paint” a cartoon with CSS. However, instead of using position absolute to style the little manga face, I decided to use CSS grid to align the eyes, nose and mouth. <a href="https://codepen.io/utilitybend/pen/eYGaJPR" target="_blank" rel="noreferrer noopener">Click here for the demo</a> :)</p> <h2 id="bonus-round-masonry">Bonus round: Masonry</h2> <picture> <source srcset="/_astro/masonry.CcDQnXbU_1LyB6x.avif 320w, /_astro/masonry.CcDQnXbU_wVHKR.avif 480w, /_astro/masonry.CcDQnXbU_ZvrAq1.avif 800w, /_astro/masonry.CcDQnXbU_rTMHt.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/masonry.CcDQnXbU_1k8CcJ.webp 320w, /_astro/masonry.CcDQnXbU_5vIR4.webp 480w, /_astro/masonry.CcDQnXbU_ZWRzjO.webp 800w, /_astro/masonry.CcDQnXbU_tNNF.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/masonry.CcDQnXbU_Z1JQHSG.jpg" srcset="/_astro/masonry.CcDQnXbU_ZlUpGz.jpg 320w, /_astro/masonry.CcDQnXbU_Z1Axj2f.jpg 480w, /_astro/masonry.CcDQnXbU_2qfvzN.jpg 800w, /_astro/masonry.CcDQnXbU_Z1Fze5D.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A masonry grid" loading="lazy" decoding="async" fetchpriority="auto" width="16" height="9"> </picture> <p>I’ve already written a <a href="https://utilitybend.com/blog/masonry-with-css-grid-finally-a-solution-without-javascript">blogpost about using masonry</a> in CSS and I’m still hoping for this to roll out on all major browsers somewhere in 2022. As for now, there are tricks on how you can achieve a <a href="https://css-tricks.com/a-lightweight-masonry-solution/" target="_blank" rel="noreferrer noopener">lightweight solution by using a CSS grid</a>.</p> <p>At the moment this is only available in Firefox Nightly after a flag. By using the following syntax:</p> <pre class="language-css" data-language="css"><code is:raw="" 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">gap</span><span class="token punctuation">:</span> 5px<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 property">grid-template-rows</span><span class="token punctuation">:</span> masonry<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>I Hope some of these demo’s helped you out. I plan to play around with this a lot more and am looking forward to learning many new techniques. Happy Gridding!</p>Brecht De Ruyte5 future HTML tags on my wishlist that could benefit accessibilityhttps://utilitybend.com/blog/5-future-html-tags-on-my-wishlist-that-could-benefit-accessibility/https://utilitybend.com/blog/5-future-html-tags-on-my-wishlist-that-could-benefit-accessibility/As we're entering the last stages of 2021, I think it's time to add a little wishlist for the future of the web. Since accessibility is getting more (deserved) notice. I thought that this year might be a good time for an HTML wishlist.Mon, 20 Dec 2021 00:00:00 GMT<picture> <source srcset="/_astro/html-acc-visual.pGmN3H9v_2qCLb4.avif 375w, /_astro/html-acc-visual.pGmN3H9v_ZAuNdm.avif 480w, /_astro/html-acc-visual.pGmN3H9v_Z2ttsjE.avif 680w, /_astro/html-acc-visual.pGmN3H9v_Z21p1qn.avif 800w, /_astro/html-acc-visual.pGmN3H9v_Z1K2fOd.avif 980w, /_astro/html-acc-visual.pGmN3H9v_ZCi59o.avif 1024w, /_astro/html-acc-visual.pGmN3H9v_22iglj.avif 1660w, /_astro/html-acc-visual.pGmN3H9v_Z16l6k3.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/html-acc-visual.pGmN3H9v_Z87slC.webp 375w, /_astro/html-acc-visual.pGmN3H9v_1TV63S.webp 480w, /_astro/html-acc-visual.pGmN3H9v_1WqWA.webp 680w, /_astro/html-acc-visual.pGmN3H9v_u1RPR.webp 800w, /_astro/html-acc-visual.pGmN3H9v_KoDs2.webp 980w, /_astro/html-acc-visual.pGmN3H9v_14nPkd.webp 1024w, /_astro/html-acc-visual.pGmN3H9v_Z1lcVY1.webp 1660w, /_astro/html-acc-visual.pGmN3H9v_IX9wp.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/html-acc-visual.pGmN3H9v_Z1Uy47r.jpg" srcset="/_astro/html-acc-visual.pGmN3H9v_Z27ALHK.jpg 375w, /_astro/html-acc-visual.pGmN3H9v_Z4xdif.jpg 480w, /_astro/html-acc-visual.pGmN3H9v_Z1WvRox.jpg 680w, /_astro/html-acc-visual.pGmN3H9v_Z1urqvg.jpg 800w, /_astro/html-acc-visual.pGmN3H9v_Z1e4ET6.jpg 980w, /_astro/html-acc-visual.pGmN3H9v_lRITm.jpg 1024w, /_astro/html-acc-visual.pGmN3H9v_Z23I3oR.jpg 1660w, /_astro/html-acc-visual.pGmN3H9v_Z122wkh.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="accessibility-starts-with-good-html">Accessibility starts with good HTML</h2> <p>Forget about ARIA and roles for a moment, The best accessibility is achieved without specific shenanigans and a strong foundation in your HTML. Too many times we’re “fixing” things for accessibility while in reality, it’s not the hardest thing to do. Perfection might be hard, I can agree to that, but a basis for an accessible web, shouldn’t be.</p> <p><strong>We get too many comments about this such as:</strong></p> <ul> <li>Our CMS or framework won’t let us output HTML that way</li> <li>The client doesn’t care anyway</li> <li>It just takes too much time</li> <li>I can’t convince my boss</li> </ul> <p>When you get your basic HTML right from the start, it shouldn’t take too much time and if your CMS or framework won’t let you output the HTML the way you want, you might want to start thinking of using something else, because in the end: it all comes back to the basic building blocks.</p> <h3 id="so-why-create-more-html-tags">So why create more HTML tags?</h3> <p>The ideas I gained over the years are mainly based on a course that I followed at the <a href="https://www.anysurfer.be/en" target="_blank" rel="noreferrer noopener">AnySurfer</a> office. They help to spread the word about accessibility, give courses and “Anysurfer labels”. They even test your website for a price. Belgium doesn’t score well in the accessibility field, there is a lot of room for improvement, but times are changing.</p> <p>When following the course there is one thing that intrigued me. Screen Readers are smarter than you might think. They can give a list with “all H2 tags on the page” or “all the links on this page” by using the <strong>rotor</strong> function. Which made me think on how we can improve accessibility for people using this kind of aid. I strongly believe that if we start adding useful new HTML tags, a good practice in accessibility could be more consistent because they are easy to learn and maintain. Creating good accessibility <strong>isn’t that hard</strong>, but it would be better if it’s <strong>easy</strong>.</p> <p>Also, never forget <a href="https://www.w3.org/TR/aria-in-html/#firstrule" target="_blank" rel="noreferrer noopener">the first rule of ARIA</a></p> <blockquote> <p>If you can use a native HTML element [HTML51] or attribute with the semantics and behaviour you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.</p> </blockquote> <p>Before I make this list, I want to point out that this is purely about some ideas that I heard, or read online and that they’re purely fictional (for now). I will include information on where I found the idea if applicable. They are by no means a “top 5”, just five in total.</p> <h2 id="1-banner-tag-for-banner-usps--promotions">1 <code>&lt;banner&gt;</code> tag for banner USP’s &amp; promotions</h2> <p>Especially with the pandemic, webshops started to rise as the skyline of New York in 1900. Imagine a native html tag for the <code>role=”banner”</code> to add promotions on a webshop. A rotor might show “all the banners on this page” and get results such as “buy 2, get one for free” on the current product page.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>banner</span><span class="token punctuation">&gt;</span></span> Buy two products, get one for free! <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>banner</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- or maybe the following? as it might be interesting to combine images and text inside the tag --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>banner</span> <span class="token attr-name">aria-labelledby</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>banner-title<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h3</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>banner-title<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Buy two products, get one for free!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h3</span><span class="token punctuation">&gt;</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">&quot;</span>some-image.jpg<span class="token punctuation">&quot;</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">&quot;</span>Promobanner: Buy two products, get one for free<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>banner</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="2-price-tag">2 <code>&lt;price&gt;</code> tag</h2> <p>Staying in the webshop story, this one could be handy as well. Especially for the way aiding devices would pronounce prices. However, I do think this would need a bit of extra, maybe <strong>a type attribute</strong> to define what kind of price we’re printing out such as “discount” or “total”. It could reduce the amount of hidden texts. We might even benefit from a currency attribute to specify that as well. One side note here is that I do feel there is a <strong>bit of danger involved</strong> in this when it comes to scamming people. We have “price tags“ in stores, now let’s add them to HTML? It kind of makes sense to me.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token comment">&lt;!-- some wild ideas here, but in this idea the price should only contain numbers --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>price</span> <span class="token attr-name">currency</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>euro<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>20,00<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>price</span><span class="token punctuation">&gt;</span></span> -<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>price</span> <span class="token attr-name">currency</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>euro<span class="token punctuation">&quot;</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">&quot;</span>discount<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>5,00<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>price</span><span class="token punctuation">&gt;</span></span> +<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>price</span> <span class="token attr-name">currency</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>euro<span class="token punctuation">&quot;</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">&quot;</span>addition<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>3,00<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>price</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>price</span> <span class="token attr-name">currency</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>euro<span class="token punctuation">&quot;</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">&quot;</span>total<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>18,00<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>price</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="3-alert-tag">3 <code>&lt;alert&gt;</code> tag</h2> <p>Truth be told, this might be my favourite of this list and let me tell you why. It seems very easy to implement in new websites or to add on older websites. This one would be great for websites who already have a bad score. Many times, forms and <strong>especially error handling can be frustrating for users with a reader</strong>. They notice something went wrong, but it’s hard to tell where the problem is located, or which field gave an error. It’s not the hardest thing to get right for those users, but if there was a way that they could “view all alerts on this page” and then read which field gave an error (and why). It could make the frustrating journey a bit better.</p> <p>So yes, this might sound like a “fixer upper” and it probably is. But I feel this is something we can benefit from when adding a small update on a website that never had decent accessibility to begin with. Every little bit helps for that matter. If this would complicate things with a JavaScript <code>alert();</code> I could settle for a <code>&lt;notice&gt;</code> tag.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>alert</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>error<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>There was an error submitting the form<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">&gt;</span></span>Email field has invalid characters<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>alert</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>alert</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>success<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Product xxxx was added in your basket. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>alert</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="4-search-tag">4 <code>&lt;search&gt;</code> tag</h2> <p>This one caused a bit of commotion on the web this year, some liked it, others were against it. But I personally think <a href="https://www.sarasoueidan.com/blog/in-quest-of-search/" target="_blank" rel="noreferrer noopener">Sara Soueidan has some excellent points when it comes to adding the search tag</a> as a sectioning element. I think that many people use the search first, whether it be in documentation or webshops. The search is often a go-to place when a user gets lost as well and could be a savior for keeping people on your website.</p> <p>It could be used for the default search forms with or without options, but maybe we could even take it a step further for when it comes to filtering a large catalog.</p> <p><strong>Update 2023:</strong> this is no longer completely fictional as there is a go for the <a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-search-element" target="_blank" rel="noreferrer noopener">HTML search element</a>.</p> <h2 id="5-chart-tag">5 <code>&lt;chart&gt;</code> tag</h2> <p>Charts are always hard for accessibility, mostly because they are a visual representation of data. When the “visual” gets removed from the representation, there isn’t much left.</p> <p>There are many ways we can tackle this by adding Screen Reader only text or captions, but having a built-in feature would be a very nice to have. I see this as an alternative to the <code>&lt;figure&gt;</code> element where the <code>&lt;figcaption&gt;</code> isn’t shown by default and contains a descriptive representation of the chart.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>chart</span><span class="token punctuation">&gt;</span></span> <span class="token comment">&lt;!-- some canvas or other html that creates the visual representation --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>chartcaption</span><span class="token punctuation">&gt;</span></span> A description of the chart, could also be a <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>table</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>table</span><span class="token punctuation">&gt;</span></span> element maybe? Is visually hidden by default. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>chartcaption</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>chart</span><span class="token punctuation">&gt;</span></span> </code></pre> <h2 id="its-time-for-html-to-rise-again">It’s time for HTML to rise again</h2> <p>It’s been <strong>7 years</strong> since HTML5 was released and I think it’s about time the language gets a bit of extra love. We’re writing ARIA helpers all the time and sometimes <strong>end up doing more damage than good</strong>. These are just some wild ideas and I’m far from an expert when it comes to the legacy of browsers.</p> <p>It’s important that we “don’t break the web” by adding new tags that might have some history behind them in older browsers. But that concludes my wishlist, who knows, maybe Santa will bring some of those presents in 2022.</p>Brecht De RuyteIs it time to stop pre-processing CSS and use post-processing instead?https://utilitybend.com/blog/is-it-time-to-stop-pre-processing-css-and-use-post-processing-instead/https://utilitybend.com/blog/is-it-time-to-stop-pre-processing-css-and-use-post-processing-instead/I've been writing CSS in a SCSS environment for 8 years by now, that's a long time and since recently I started playing around with PostCSS. PostCSS has been around for some time, but I finally found the chance to dig in.Mon, 06 Dec 2021 00:00:00 GMT<picture> <source srcset="/_astro/postcss-and-sass-logo.BjHnoE9w_Z25mLJb.avif 375w, /_astro/postcss-and-sass-logo.BjHnoE9w_29tEVp.avif 480w, /_astro/postcss-and-sass-logo.BjHnoE9w_Zio5IQ.avif 680w, /_astro/postcss-and-sass-logo.BjHnoE9w_Z2e0WDx.avif 800w, /_astro/postcss-and-sass-logo.BjHnoE9w_Z1Jj5sB.avif 980w, /_astro/postcss-and-sass-logo.BjHnoE9w_EXWXi.avif 1024w, /_astro/postcss-and-sass-logo.BjHnoE9w_Zr2NQM.avif 1660w, /_astro/postcss-and-sass-logo.BjHnoE9w_2nXKJI.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/postcss-and-sass-logo.BjHnoE9w_Z1L6Eb1.webp 375w, /_astro/postcss-and-sass-logo.BjHnoE9w_2sJMuz.webp 480w, /_astro/postcss-and-sass-logo.BjHnoE9w_R1Oj.webp 680w, /_astro/postcss-and-sass-logo.BjHnoE9w_Z1TJP5n.webp 800w, /_astro/postcss-and-sass-logo.BjHnoE9w_Z1q2WTr.webp 980w, /_astro/postcss-and-sass-logo.BjHnoE9w_hLvGv.webp 1024w, /_astro/postcss-and-sass-logo.BjHnoE9w_ZOfg8z.webp 1660w, /_astro/postcss-and-sass-logo.BjHnoE9w_L3bQc.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/postcss-and-sass-logo.BjHnoE9w_ZgsxM0.jpg" srcset="/_astro/postcss-and-sass-logo.BjHnoE9w_ZIygJu.jpg 375w, /_astro/postcss-and-sass-logo.BjHnoE9w_Z1ySWRP.jpg 480w, /_astro/postcss-and-sass-logo.BjHnoE9w_13ppfP.jpg 680w, /_astro/postcss-and-sass-logo.BjHnoE9w_ZRcrDQ.jpg 800w, /_astro/postcss-and-sass-logo.BjHnoE9w_ZnuzsU.jpg 980w, /_astro/postcss-and-sass-logo.BjHnoE9w_24xyga.jpg 1024w, /_astro/postcss-and-sass-logo.BjHnoE9w_WvLq5.jpg 1660w, /_astro/postcss-and-sass-logo.BjHnoE9w_6KTsf.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Post CSS and Sass logo" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="whats-the-difference-between-pre--and-post-processing-css">What’s the difference between pre- and post-processing CSS?</h2> <p>As the name might suggest, pre-processing happens before and post-processing happens after, but there is a little more to it. When we do pre-processing it always involves another language, such as Sass/SCSS (which I’m most familiar with) but also LESS and Stylus. When pre-processing we’re writing our styles in another language and turning it into CSS.</p> <p>Post-processing happens later on, we already write CSS but add plugins to enhance it such as automatically adding prefixes, purging unused styles or add fallbacks for older browsers that don’t understand certain features.</p> <h2 id="the-main-benefits-of-using-pre-processing">The main benefits of using pre-processing</h2> <p>It has a lot of things that make our life a little easier such as mixins, nesting and variables. The reason I started working with them 8 years ago was just for that: nesting and variables. Also the automatic prefixing was very needed at the time. The good thing is that it continues to evolve, we can write a lot of functions now, and use arrays in our variables. One thing is for certain: it makes writing (S)CSS a lot faster.</p> <h2 id="anno-2022-can-we-drop-pre-processing-and-only-post-process-our-css">Anno 2022, can we drop pre-processing and only post-process our CSS?</h2> <p>Yes we can! But there are a few things that we need to keep in mind and I will explain why I think preprocessors are becoming a thing of the past. As mentioned before, I started playing around with this in my spare time and later on I got some time to figure things out on how to do it on a team-level for my job. I will try and explain my findings for both of these cases. And the answer will differ between the two. Let’s start with the basics:</p> <h3 id="variables-are-native-css-now">Variables are native CSS now</h3> <p>We can write variables, it’s native to CSS. With the exclusion of array variables we can pretty much add every color, font, measuring unit in our CSS variables. There is one thing missing though and that’s adding media query variables. It’s a work in progress but it will be available as it’s currently <a href="https://www.w3.org/TR/mediaqueries-5/#custom-mq" target="_blank" rel="noreferrer noopener">a working draft by the W3C</a>.</p> <p>If only there was a way we could write them now (spoiler alert: there is).</p> <h3 id="nesting-has-been-a-work-in-progress-for-a-long-time-by-the-w3c">Nesting has been a work in progress for a long time by the W3C</h3> <p>Currently, this is a <a href="https://www.w3.org/TR/css-nesting-1/" target="_blank" rel="noreferrer noopener">first public working draft</a>, which is a soft way of telling you that this is far from a final idea and it might get thrown away. It might still be dangerous to see this as a final syntax, but I noticed that a lot of people started working with it in combination with some PostCSS plugins. It works a bit different than nesting in Sass and it takes some getting used to.</p> <p><strong>Nesting in Sass/Scss:</strong></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 1024px<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 selector">h1</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 punctuation">}</span> </code></pre> <p><strong>Nesting with the nesting module enabled in PostCSS:</strong></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 1024px<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 selector">&amp; h1</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 punctuation">}</span> </code></pre> <p>It seems that this is not an easy draft to work on with many questions and fallback issues, in the end: “we can’t break the web”. It’s something that is high on my list for the future of CSS and I hope it will see the light in some development browsers such as Canary and Nightly somewhere in 2022.</p> <p>My basic setup for using custom media queries, importing files, auto-prefixing and nesting was the following</p> <pre class="language-javascript" data-language="javascript"><code is:raw="" class="language-javascript"><span class="token keyword">const</span> postcssPresetEnv <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">&#39;postcss-preset-env&#39;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">&quot;postcss-import&quot;</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">postcssPresetEnv</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">stage</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token literal-property property">browsers</span><span class="token operator">:</span> <span class="token string">&#39;last 2 versions, &gt; 0.2%, not dead&#39;</span><span class="token punctuation">,</span> <span class="token literal-property property">features</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">&#39;custom-media-queries&#39;</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string-property property">&#39;nesting-rules&#39;</span><span class="token operator">:</span> <span class="token boolean">true</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> <span class="token punctuation">}</span><span class="token punctuation">;</span> </code></pre> <h3 id="conclusion-of-basic-features">Conclusion of basic features</h3> <p>Yes, there are ways to tackle the nesting and custom media queries in PostCSS. There are many plugins for that, the <a href="http://preset-env.cssdb.org/" target="_blank" rel="noreferrer noopener">postcss-preset-env</a> plugin seems to be the most popular one for this as it also includes a browser list support. However, this is only a small part of why we use Sass. The strength is mainly in the functions and mixins that we can write. Although I do believe mixins usually create a lot of code that doesn’t end up in the final product, they are beneficial when used correctly.</p> <h2 id="there-is-a-plugin-for-that">There is a plugin for that!?</h2> <p>PostCSS is full of handy plugins that can turn our CSS to a more Sass-like environment. There are plugins for:</p> <ul> <li>Adding imports to a single file</li> <li>Writing for loops in your css</li> <li>Adding Sass-like variables</li> </ul> <p>You might understand that this feels a bit strange, we’re actually creating our own environment now, and although it works like a charm, you might be thinking “why not just use Sass then?”. This is a fair point. I personally enjoy <a href="https://postcss.org/" target="_blank" rel="noreferrer noopener">PostCSS</a> because it lets me think about my code, and by using future CSS with the (probably) correct syntaxes, I strongly believe it makes me a better developer. I won’t get into detail on these just yet, but it’s important that you know that we can tweak our environment using PostCSS as well.</p> <p>PostCSS should not be considered as purely an alternative for Sass. I look at them as separate things. Want to write Sass, then by all means do so. But if you want to write CSS, consider dropping Sass completely for a little project and be amazed by what we can do without using it. Personally, I found it eye-opening to step out of that comfort zone.</p> <h3 id="extra-benefits-of-postcss-or-postprocessing-in-general">Extra benefits of PostCSS or postprocessing in general</h3> <p>With post-processing we can do a bit more. There are a few plugins I would like to highlight:</p> <h4 id="purgecss">PurgeCSS</h4> <p>The plugin looks at your files (html, js, twig,…) and only adds the CSS based on the classnames found in them. A really nice way to optimise your final CSS-file just a little further. <a href="https://purgecss.com/" target="_blank" rel="noreferrer noopener">Read more about purging css</a>.</p> <h4 id="cssnano">CSSNANO</h4> <p>Minifies your files and has many options to choose from. There are a lot of alternatives for this, but I was quite happy about how this handled my files. <a href="https://cssnano.co/" target="_blank" rel="noreferrer noopener">Read more about CSSNano here</a>.</p> <h4 id="sort-media-queries">Sort media queries</h4> <p>Automatically <a href="https://github.com/solversgroup/postcss-sort-media-queries" target="_blank" rel="noreferrer noopener">combines the media queries</a> and adds them to the end of the CSS file, quite handy when nesting to lower the file size.</p> <h2 id="im-actually-a-bit-angry-because-of-my-own-scss-comfort-zone">I’m actually a bit angry because of my own SCSS comfort zone</h2> <p>A few months back, there was a tweet from Sass that made me think quite a lot:</p> <figure class="thirds image"><picture> <source srcset="/_astro/sass-tweet.D3qR93D__Z1qgWzI.avif 320w, /_astro/sass-tweet.D3qR93D__Z29CuWr.avif 480w, /_astro/sass-tweet.D3qR93D__Z2gf3d.avif 601w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sass-tweet.D3qR93D__Z6RWXO.webp 320w, /_astro/sass-tweet.D3qR93D__ZPevlx.webp 480w, /_astro/sass-tweet.D3qR93D__1h7JxG.webp 601w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sass-tweet.D3qR93D__1Bh0Er.png" srcset="/_astro/sass-tweet.D3qR93D__Z1PfoX5.png 320w, /_astro/sass-tweet.D3qR93D__2vAbt8.png 480w, /_astro/sass-tweet.D3qR93D__ZreGqz.png 601w" sizes="(max-width: 800px) 100vw, 80vw" alt="Tweet from Sass: The driving example is a new CSS conditional rule, it was orinally proposed as @when, but some WG members want to change it to @if - despite of sass's @if directive being released 13 years ago!" loading="lazy" decoding="async" fetchpriority="auto" width="180" height="71"> </picture><figcaption>Tweet from Sass on 17th of september 2021</figcaption></figure> <p>Although writing in SCSS for some time now, this actually made the purist in me a little angry. There is an <a href="https://tabatkins.github.io/specs/css-when-else/" target="_blank" rel="noreferrer noopener">unofficial draft in CSS about using @when native</a>. However, a few members of the working group wanted to change this to an “if” statement, which would clash with the current implementation in Sass. To be honest, I know Sass has supported this for a long time, but a tool should never dictate what a product looks like, especially if the tool is dependent on the product itself. This is harsh and I don’t feel like changing all my SCSS files as well, but it’s a simple truth. You don’t make a wrench for an 8-sided nut and complain that when the 8-sided nut releases, it has a slightly different size. In that world, machines would be made differently as well, so the tool would dictate a little too much. This is mainly why I finally started looking into PostCSS.</p> <p>We live in a fantastic time with browsers that automatically update, the progression of CSS is going much faster now and should be given the room to do so, in a way that makes sense.</p> <p><a href="https://github.com/w3ctag/design-principles/issues/335" target="_blank" rel="noreferrer noopener">Grab some popcorn and read the full discussion here</a>.</p> <h2 id="final-thoughts">Final thoughts</h2> <p>I have the feeling that having a setup in PostCSS somehow forces me to write better CSS, using future modules is a nice to have since it makes me study while I’m working on a project and keeps me in the loop of evolutions on the W3C part of it. As a personal tool to ditch SCSS all together I really love it. Especially when features get rolled out to a browser, it’s easy to just remove a plugin since it isn’t needed anymore. What’s even better is that with tools such as browser lists, this will happen automatically.</p> <p>When it comes to bigger teams, I think there could be more issues: a lot of people know how to write in SCSS so everything you configure is a custom setup and good defaults will need to be discussed. This might be easy with a 2-3 person team, but in larger teams things could get a bit overwhelming. If you use this in a big team: I’d really love to hear your thoughts on it through linkedIn or twitter.</p> <p>In the end, I think a Hybrid model is still the best way to go, using SCSS together with PostCSS might give you the best of two worlds.</p>Brecht De RuyteStyling based on container width made possible with CSS container queries.https://utilitybend.com/blog/styling-based-on-container-width-made-possible-with-css-container-queries/https://utilitybend.com/blog/styling-based-on-container-width-made-possible-with-css-container-queries/I've been hyped about the new container queries in CSS. The possibility to change the look & feel of elements based on the location they are presented instead of the viewport width will be a great addition to the front-end workflow.Tue, 02 Nov 2021 00:00:00 GMT<picture> <source srcset="/_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_1f0jNU.avif 375w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_1UQg9s.avif 480w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Evh11.avif 680w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_28ze5B.avif 800w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z20H3mM.avif 980w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_1JXsyE.avif 1024w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_KFQOk.avif 1660w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z1uYYEj.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z1gxHN8.webp 375w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_ZzGLsA.webp 480w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z1Q2KB2.webp 680w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_ZmXNwr.webp 800w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_xV2O6.webp 980w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_ZkGtQ.webp 1024w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_2a8l2Q.webp 1660w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z6xvqM.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_1FOfEd.jpg" srcset="/_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z1qwYbJ.jpg 375w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_ZJG2Qc.jpg 480w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_Z2121YD.jpg 680w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_ZwX4U3.jpg 800w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_nVLqu.jpg 980w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_e7kh.jpg 1024w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_2rOEvU.jpg 1660w, /_astro/frank-mckenna-tjX_sniNzgQ-unsplash.fouANPJ3_b8N2h.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="frank-mckenn - A bunch of containers" loading="eager" decoding="async" fetchpriority="auto" width="3981" height="2595" class="img-fluid"> </picture><p>It all started with a proposal by a few people including <a href="https://css.oddbird.net/rwd/query/contain/" target="_blank" rel="noreferrer noopener">Miriam Suzanne</a> who drew the first ideas into how containment should behave. With a lot of upvotes on the topic and more people backing it as a highly-needed feature, it’s finally available with a flag on Chrome Canary with many more browsers to follow soon.</p> <p>We’ve all been there, you have a beautifully styled call to action in the main content section of a website, after some A/B testing or client feedback, there is a decision that the call to action would be better off in the sidebar. Most of the time we start adding some classnames to the card or its parent and change their look based on the media queries. It does the trick, we’ve done it for some while now, but what if we can make all of this a little bit more dynamic, readable and maintainable.</p> <p>I created a <a href="https://codepen.io/utilitybend/pen/zYdEoax?editors=1100" target="_blank" rel="noreferrer noopener">little example to illustrate the mass use of media queries</a> to get to a certain result. I’m pretty sure I could write this example with less code, but if we’re honest, when working with deadlines and project rushing, this isn’t far away from the truth.</p> <blockquote> <p>I created this article when the container queries were still behind a flag. However. I updated the article on 30 september 2022 to be a bit more up to date with stable releases in Chrome and Safari</p> </blockquote> <h2 id="how-will-container-queries-with-css-help-us">How will container queries with CSS help us?</h2> <p><s><p>First of all, if you want to view the demo’s provided in this blogpost, you will have to <strong>use chrome canary</strong> for now, go to <code>chrome://flags</code> and search for <code>Enable CSS Container Queries</code> and enable it.</p></s></p> <p>Container queries are getting a lot of support lately. Check if your browser can <a href="https://caniuse.com/?search=container%20queries" target="_blank" rel="noreferrer noopener">handle this new feature</a>.</p> <p>Rather than relying on the viewport, we will be able to change the behaviour of our component based on the way it’s contained. This basically means that we won’t have to provide extra classes and will be able to style component based, rather than viewport based. In short: CSS container queries will add many benefits to the fluid web.</p> <h2 id="how-does-it-all-work">How does it all work?</h2> <p>First of all we’ll need to <strong>specify the container</strong> for the parent(s) of our elements. This is done with the container property. The container property is a shorthand for the following:</p> <p><code>container-type</code><br> Defines an element as a query container. Descendents can query aspects of its sizing, layout, and style.</p> <p><code>container-name</code><br> Specifies a list of query container names for @container rules to use to filter which query containers are targeted.</p> <p><code>container</code>:<br> A shorthand property to set both <code>container-type</code> and <code>container-name</code>.</p> <p><strong>The container-type can have the following values:</strong></p> <p><code>size</code><br> Establishes a query container for dimensional queries on the block and inline axis. Applies layout, style, and size containment to the element.</p> <p><code>inline-size</code><br> Establishes a query container for dimensional queries on the inline axis of the container. Applies layout, style, and inline-size containment to the element.</p> <p><code>normal</code><br> The element is not a query container for any dimensional queries on the block and inline axis.</p> <p><em>More information on this can be found on the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries" target="_blank" rel="noreferrer noopener">MDN Web Docs</a>.</em></p> <h3 id="basic-example-of-the-container-property">Basic example of the container property.</h3> <p>Let’s say we want to style a card differently based on the width of its current container. In this example our sidebar is at least 400px wide and we want to style the card in a horizontal grid when it’s bigger than the sidebar’s width. We want to create the following:</p> <picture> <source srcset="/_astro/main-content-and-sidebar.B7Iirzt2_Z155PPC.avif 320w, /_astro/main-content-and-sidebar.B7Iirzt2_c89eB.avif 480w, /_astro/main-content-and-sidebar.B7Iirzt2_KwrAu.avif 800w, /_astro/main-content-and-sidebar.B7Iirzt2_Z1k5XJO.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/main-content-and-sidebar.B7Iirzt2_ZuQ8mx.webp 320w, /_astro/main-content-and-sidebar.B7Iirzt2_LmQHG.webp 480w, /_astro/main-content-and-sidebar.B7Iirzt2_1kLa4z.webp 800w, /_astro/main-content-and-sidebar.B7Iirzt2_ZJQggJ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/main-content-and-sidebar.B7Iirzt2_1h86uf.png" srcset="/_astro/main-content-and-sidebar.B7Iirzt2_eEdUD.png 320w, /_astro/main-content-and-sidebar.B7Iirzt2_1vSe0R.png 480w, /_astro/main-content-and-sidebar.B7Iirzt2_25hwmK.png 800w, /_astro/main-content-and-sidebar.B7Iirzt2_ZkSXy.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A page with main content and a sidebar" loading="lazy" decoding="async" fetchpriority="auto" width="1960" height="1390"> </picture> <p>So the HTML might look something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>content<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>card<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://picsum.photos/800/600?random=1<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Some rondom title in here<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>card-description<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Read more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>card<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://picsum.photos/800/600?random=2<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Some rondom title in here<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>card-description<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Read more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>aside</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>sidebar<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>card<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://picsum.photos/800/600?random=3<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Some rondom title in here<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>card-description<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Read more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>aside</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>And then we handle the <strong>CSS container query</strong>:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* Basic card styling */</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid #444<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>25<span class="token punctuation">,</span> 35<span class="token punctuation">,</span> 51<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-body</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* container magic, make cards change look &amp; feel based by their container width instead of the viewport */</span> <span class="token selector">.grid &gt; *</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> card/inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@container</span> card <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 401px<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">display</span><span class="token punctuation">:</span> grid<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 15px<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 100px 1fr<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 15px<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">.card-body</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 selector">.card-description</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> <p>All done! I created an example which goes a bit more in-depth:</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/oNezMOe?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h3 id="a-few-things-to-note">A few things to note</h3> <p>When this feature was still behind a flag, giving a name to the container was an extra option. After a lot of discussions at the W3C working group it has been made mandatory.</p> <h2 id="inline-size-vs-block-size-containment">Inline-size vs block-size containment.</h2> <p>In the example above, I used the <code>inline-size</code> because I only needed to change the behaviour based on the container’s width. There has been talk about the possibility to use <code>block-size</code> as well, so that we can change the behaviour based on the height. Unfortunately, this is not yet supported. But the possibilities of this will be amazing. I already <a href="https://codepen.io/utilitybend/pen/PoKKVOQ" target="_blank" rel="noreferrer noopener">created a little template</a> to start working on it as soon as it’s available.</p> <h2 id="benefits-of-contain-with-content-provided-by-a-cms">Benefits of “contain” with content provided by a CMS.</h2> <p>When writing and experimenting with new CSS features I always try to imagine real-life problems I had to deal with in the past. And this one is very common and something that others have experienced as well.</p> <p><strong>The case:</strong> When creating a homepage for a client, there might be a “in the picture” section. The client is allowed to add up to three items in the CMS. But from time to time they just want to add one big banner, or maybe two items. When this happens, we mostly rely on counting the items (through PHP or JS maybe?) so that we can add different class names based on the length of an array. Sounds familiar?</p> <p>By using container queries such as the following we can handle this behaviour perfectly without relying on extra class names <strong>by providing a fluid CSS grid in combination with CSS container queries:</strong></p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token comment">&lt;!-- When the CMS gives 2 items as an output (feel free to change the amount of items) --&gt;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>subtitle<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>CMS outputs 2 items<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>container grid-cta<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>grid-cta-item<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>card-cta<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://picsum.photos/1600/900?random=1<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Call to action nr 1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>card-description<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Read more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</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">&quot;</span>grid-cta-item<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <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">&quot;</span>card-cta<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>https://picsum.photos/1600/900?random=2<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Call to action nr 2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>card-description<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</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">&quot;</span><span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Read more<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>article</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token comment">/* basic styling of our card */</span> <span class="token selector">.card-cta</span> <span class="token punctuation">{</span> <span class="token property">margin-bottom</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid #444<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">rgba</span><span class="token punctuation">(</span>25<span class="token punctuation">,</span> 35<span class="token punctuation">,</span> 51<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-body</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.4rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* Setting up our cta grid */</span> <span class="token selector">.grid-cta</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>320px<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">grid-gap</span><span class="token punctuation">:</span> 1.4rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.grid-cta-item</span> <span class="token punctuation">{</span> <span class="token property">container</span><span class="token punctuation">:</span> cta/inline-size<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">/* change style of our cards based on their container width */</span> <span class="token atrule"><span class="token rule">@container</span> cta <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 560px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card-cta</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> 1.4rem<span class="token punctuation">;</span> <span class="token property">grid-template-columns</span><span class="token punctuation">:</span> 215px 1fr<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.4rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-body</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 atrule"><span class="token rule">@container</span> cta <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 700px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.card-cta</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">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<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-cta::after</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> 0<span class="token punctuation">;</span> <span class="token property">left</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">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> <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.2<span class="token punctuation">)</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 property">content</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-body</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> 50%<span class="token punctuation">;</span> <span class="token property">left</span><span class="token punctuation">:</span> 50%<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 500px<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">transform</span><span class="token punctuation">:</span> <span class="token function">translate</span><span class="token punctuation">(</span>-50%<span class="token punctuation">,</span> -50%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">z-index</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>This really shows the strength of the container queries.The end results gives us something like this:</p> <p>The CMS provides 3 items.</p> <picture> <source srcset="/_astro/container-queries-cms-output.BfJJtG5M_Z1n96Uq.avif 320w, /_astro/container-queries-cms-output.BfJJtG5M_Z1y2fxl.avif 480w, /_astro/container-queries-cms-output.BfJJtG5M_1o7c1R.avif 800w, /_astro/container-queries-cms-output.BfJJtG5M_22JIIi.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/container-queries-cms-output.BfJJtG5M_1VAxAW.webp 320w, /_astro/container-queries-cms-output.BfJJtG5M_1KHoY2.webp 480w, /_astro/container-queries-cms-output.BfJJtG5M_ZmkhfG.webp 800w, /_astro/container-queries-cms-output.BfJJtG5M_hifqJ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/container-queries-cms-output.BfJJtG5M_ZD4Mtw.png" srcset="/_astro/container-queries-cms-output.BfJJtG5M_Z1CIEST.png 320w, /_astro/container-queries-cms-output.BfJJtG5M_Z1NBNvO.png 480w, /_astro/container-queries-cms-output.BfJJtG5M_18wD3o.png 800w, /_astro/container-queries-cms-output.BfJJtG5M_1MaaJO.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Three cards in a row with grid and container queries" loading="lazy" decoding="async" fetchpriority="auto" width="1960" height="750"> </picture> <p>The CMS provides only one item:</p> <picture> <source srcset="/_astro/cta-1.Bj7smVQp_2jIBzW.avif 320w, /_astro/cta-1.Bj7smVQp_Kro3M.avif 480w, /_astro/cta-1.Bj7smVQp_ZVgzv5.avif 800w, /_astro/cta-1.Bj7smVQp_1uWAY0.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/cta-1.Bj7smVQp_Z1wUQOM.webp 320w, /_astro/cta-1.Bj7smVQp_1XY3rY.webp 480w, /_astro/cta-1.Bj7smVQp_hg4S7.webp 800w, /_astro/cta-1.Bj7smVQp_Z2lGRqJ.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/cta-1.Bj7smVQp_Z17f3Vx.png" srcset="/_astro/cta-1.Bj7smVQp_Zit3kA.png 320w, /_astro/cta-1.Bj7smVQp_Z1QKgQK.png 480w, /_astro/cta-1.Bj7smVQp_1vHSnj.png 800w, /_astro/cta-1.Bj7smVQp_Z17f3Vx.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Full width card example with container queries" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="661"> </picture> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/MWvvZZq?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="using-container-queries-with-polyfills">Using container queries with polyfills.</h2> <p>Yes, you can start using this awesome feature right now with a polyfill if you need to support older browsers. Personally I would advice using it with <a href="https://postcss.org/" target="_blank" rel="noreferrer noopener">PostCSS</a>. I won’t go in-depth on this because it’s very well documented. However I might add a demo of this on github in the near future.</p> <p><a href="https://github.com/jsxtools/cqfill" target="_blank" rel="noreferrer noopener">Check out the polyfill for container queries here</a>.</p> <h2 id="are-container-queries-performant">Are container queries performant?</h2> <p>There isn’t a final metric on this. However, there are certain benchmarks on which these new features are tested and as many more browsers are starting the development of it, I’m pretty sure it won’t be a problem.</p> <h2 id="further-experimentation-and-reads">Further experimentation and reads.</h2> <p>There are <a href="https://codepen.io/collection/XQrgJo" target="_blank" rel="noreferrer noopener">many codepens already showing this feature</a> and even I can’t help myself adding a little experiment. Such as this 5-frame animation based on container width (Really, don’t use this kind of thing in your live projects, it’s just me having a bit of fun with CSS).</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/jOLmgdQ?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h3 id="want-to-go-more-in-depth-here-are-some-reads-for-you">Want to go more in depth? Here are some reads for you:</h3> <ul> <li> <a href="https://ishadeed.com/article/say-hello-to-css-container-queries/" target="_blank" rel="noreferrer noopener">Say hello to CSS container queries</a> </li> <li> <a href="https://drafts.csswg.org/css-contain-3/" target="_blank" rel="noreferrer noopener">CSS containment module</a> </li> <li> <a href="https://codepen.io/collection/XQrgJo" target="_blank" rel="noreferrer noopener">Collection of pens by Miriam Suzanne</a> </li> </ul> <p><strong>Note</strong>: I updated the examples on september 30, 2022 with the new specification</p>Brecht De RuyteSticky headers: a solution with UX pitfalls?https://utilitybend.com/blog/sticky-headers-a-solution-with-ux-pitfalls/https://utilitybend.com/blog/sticky-headers-a-solution-with-ux-pitfalls/Let's talk about sticky headers. They get used all the time and it seems that they are something we expect on a website. But is adding a sticky header the solution to our UX/UI problems, or just an easy fix?Tue, 19 Oct 2021 00:00:00 GMT<picture> <source srcset="/_astro/main-image.D3nYPxhd_Z1ahtsK.avif 375w, /_astro/main-image.D3nYPxhd_118KmD.avif 480w, /_astro/main-image.D3nYPxhd_8Wxlq.avif 680w, /_astro/main-image.D3nYPxhd_htSpI.avif 800w, /_astro/main-image.D3nYPxhd_1gobqG.avif 980w, /_astro/main-image.D3nYPxhd_dtXJv.avif 1024w, /_astro/main-image.D3nYPxhd_ZNcWuy.avif 1660w, /_astro/main-image.D3nYPxhd_1lxxlP.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/main-image.D3nYPxhd_ZQQGVW.webp 375w, /_astro/main-image.D3nYPxhd_1jywSr.webp 480w, /_astro/main-image.D3nYPxhd_rnjRe.webp 680w, /_astro/main-image.D3nYPxhd_zTEVw.webp 800w, /_astro/main-image.D3nYPxhd_1yNWWu.webp 980w, /_astro/main-image.D3nYPxhd_ZzN9Is.webp 1024w, /_astro/main-image.D3nYPxhd_Z1Bv5Xw.webp 1660w, /_astro/main-image.D3nYPxhd_1IjWIw.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/main-image.D3nYPxhd_Z1c4Qg7.jpg" srcset="/_astro/main-image.D3nYPxhd_Z2u2mka.jpg 375w, /_astro/main-image.D3nYPxhd_ZiB7tL.jpg 480w, /_astro/main-image.D3nYPxhd_Z1aMkuY.jpg 680w, /_astro/main-image.D3nYPxhd_Z12fYqG.jpg 800w, /_astro/main-image.D3nYPxhd_Z3lGpI.jpg 980w, /_astro/main-image.D3nYPxhd_Z8eekI.jpg 1024w, /_astro/main-image.D3nYPxhd_Z19VazM.jpg 1660w, /_astro/main-image.D3nYPxhd_ttbkA.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Sticky post-it with skull on it" loading="eager" decoding="async" fetchpriority="auto" width="2300" height="1294" class="img-fluid"> </picture><h2 id="ask-yourself-the-question-why-do-we-need-a-sticky-header">Ask yourself the question: why do we need a sticky header?</h2> <p>This question might sound a bit silly but actually, we don’t ask it enough. We always assume we made the right call by using them because: who can say no to it? “It just works”. And you’re right! It probably does.</p> <p>If you would look at the essence of the question you might get the following answers:</p> <ol> <li>We want to be able to <strong>navigate</strong> the whole time</li> <li>We want to know which part of the website is <strong>active</strong> at the moment</li> <li>We want the <strong>logo / branding</strong> to be visible over the entire website</li> </ol> <p>All of these are fair points, but I could still ask you the same question: do you need a sticky header to achieve this or is it just an easy solution? I’m not calling a “death to the sticky header” in this article, but the idea is to make you think and try to find creative solutions.</p> <p>It’s important to notice the different media when we talk about sticky headers. We have a different screen size and generally have a different aspect ratio as well. We usually take a mobile first approach but to make my point, let’s start with the big screens.</p> <h2 id="desktop-sticky-headers">Desktop sticky headers.</h2> <p>When designing with the desktop in mind, sticky headers are usually not much of a problem. We have enough vertical space for the header, but even in this case, some pitfalls can be found. If you’re a weirdo like me who doesn’t always use his browser full-screen, a sticky header might be a bit too much. There are some factors that come into play here:</p> <ul> <li>What is the <strong>height</strong> of the sticky header?</li> <li>How do we <strong>decide</strong> if there is enough room for a sticky header?</li> <li>Do we <strong>expect the user</strong> to rely on the main navigation?</li> </ul> <p>Let’s get the last bullet point out of the way, because this is subjective to the kind of application.**<br> I see this far too many times:** I’m visiting a webshop, the main navigation consists out of the following:</p> <ol> <li>Home (or a logo click)</li> <li>Products</li> <li>FAQ</li> <li>About us</li> <li>Contact</li> </ol> <p><strong>Let me say this bluntly:</strong> I’m here to buy some stuff and I couldn’t care less about your organisation. The only kind of contact I want is the handover of my ordered goods on my doorstep. I really hope that your homepage doesn’t have the most important information and is rather a collection of items. I’m sure it’s a beautiful homepage with a great intro and a huge banner with your promotions, but I just want to buy this product I found while searching on google.</p> <p>When it comes to the FAQ, I’m not interested in finding that in the main navigation. If I have a question about delivery or products, I’m probably already browsing a product or even in the checkout process. Why not add a link from one of those pages? Or even better, show the answers right away.<br> What’s even worse, is that in a lot of cases, the navigation gets removed while I’m checking out: Where did my FAQ-link go? Is this some shady trick to make me follow the process anyway? There might have been some important information there such as: “why doesn’t this ship to my country? Why is the delivery going to take so long?<br> So maybe the FAQ related to the checkout process should be found on those pages instead of the main navigation where I’ll have to go to an overview and click deeper until I find delivery information. Do not assume your users will just open the FAQ link in a new tab, they probably won’t.</p> <p>If you have a main navigation that includes your shopping categories, in that case: i’m all for that. But if it’s just random information, let’s fill the screen with some more products, ok?</p> <h3 id="maybe-the-metrics-are-wrong">Maybe the metrics are wrong?</h3> <p>Let’s get a bit more technical and talk about sizes. A lot of sticky headers are just too big and first of all I want to suggest to either <strong>keep them small and tidy</strong> or at least create a smaller version when scrolling the page. For desktop screens try to keep it around 120px in height.</p> <picture> <source srcset="/_astro/sticky-window.Al_6uFIv_2hucTp.avif 320w, /_astro/sticky-window.Al_6uFIv_rVk2X.avif 480w, /_astro/sticky-window.Al_6uFIv_ZFgYDP.avif 800w, /_astro/sticky-window.Al_6uFIv_ZSU7z3.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/sticky-window.Al_6uFIv_Z1o0k13.webp 320w, /_astro/sticky-window.Al_6uFIv_1QCUVr.webp 480w, /_astro/sticky-window.Al_6uFIv_IpBeD.webp 800w, /_astro/sticky-window.Al_6uFIv_uLtjq.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/sticky-window.Al_6uFIv_ZbTl1s.png" srcset="/_astro/sticky-window.Al_6uFIv_Z25G9lV.png 320w, /_astro/sticky-window.Al_6uFIv_19W6Ay.png 480w, /_astro/sticky-window.Al_6uFIv_1ILSK.png 800w, /_astro/sticky-window.Al_6uFIv_ZbTl1s.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="sticky window" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="641"> </picture> <p>Another problem we face are those weirdo’s such as myself who like to put their browser windows in a strange aspect ratio. Usually, the metric for keeping a sticky header large or small is based on the viewport width, automatically assuming that a person is browsing at a 16:10 (ish) ratio. A simple solution for that is to also <strong>check your header based on the aspect ratio and height, not just the width.</strong></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">min-aspect-ratio</span><span class="token punctuation">:</span> 16/8<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* A minimum aspect ratio */</span> <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-height</span><span class="token punctuation">:</span> 800px<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* A minimum height */</span> <span class="token punctuation">}</span> </code></pre> <h2 id="mobile-sticky-headers">Mobile sticky headers.</h2> <p>This is the part where we tend to get a bit lazy. The most common mobile menu on websites looks like this:</p> <picture> <source srcset="/_astro/phone.DbvrBp0r_ZJ3E9q.avif 320w, /_astro/phone.DbvrBp0r_20S1L5.avif 480w, /_astro/phone.DbvrBp0r_Z1dLpwj.avif 800w, /_astro/phone.DbvrBp0r_1y38F8.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/phone.DbvrBp0r_Z11ELhL.webp 320w, /_astro/phone.DbvrBp0r_1IgTCJ.webp 480w, /_astro/phone.DbvrBp0r_Z1vnwEE.webp 800w, /_astro/phone.DbvrBp0r_1gr1wM.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/phone.DbvrBp0r_ZGHkjs.jpg" srcset="/_astro/phone.DbvrBp0r_25n0EU.jpg 320w, /_astro/phone.DbvrBp0r_ZeRrdv.jpg 480w, /_astro/phone.DbvrBp0r_1AEfi2.jpg 800w, /_astro/phone.DbvrBp0r_ZGHkjs.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Phone sticky header with menu on top" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="613"> </picture> <p>Just add the navigation in the sticky header, and done…</p> <p>So, this is a sticky header with a logo and a hamburger icon to access the navigation. The benefit is that we are now scrolling on a mobile page, so a quick flick of the scroll wheel might not bring us to the top of the page. The sticky header is actually a good practice for this. However, there is something fundamentally wrong in this example.</p> <p>When it comes to phones, the user is usually <strong>holding it with one hand</strong> and navigating with his/her thumb. In this case, we might have a hard time reaching the button by using our thumb and we also blocked a bit of space for our content on a device that could benefit from it. It also depends on the phone. For smaller phones, the top right is actually quite accessible, but for those big phones, it might be a bit tougher. Same goes here: Media queries based on height are a possible solution.</p> <p>A great fix for those ultra large phones would be putting the navigation icon on the bottom of the page and creating a sticky hamburger icon.</p> <picture> <source srcset="/_astro/phone-menu-bottom.CzDBd3Xa_Z20wfrc.avif 320w, /_astro/phone-menu-bottom.CzDBd3Xa_158UJ5.avif 480w, /_astro/phone-menu-bottom.CzDBd3Xa_Z1t3w2L.avif 800w, /_astro/phone-menu-bottom.CzDBd3Xa_Z1G5wJJ.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/phone-menu-bottom.CzDBd3Xa_ZiPjWA.webp 320w, /_astro/phone-menu-bottom.CzDBd3Xa_Z2imhAf.webp 480w, /_astro/phone-menu-bottom.CzDBd3Xa_dCoqP.webp 800w, /_astro/phone-menu-bottom.CzDBd3Xa_AnIR.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/phone-menu-bottom.CzDBd3Xa_ZGTHFY.jpg" srcset="/_astro/phone-menu-bottom.CzDBd3Xa_Z11lqnr.jpg 320w, /_astro/phone-menu-bottom.CzDBd3Xa_24jJMP.jpg 480w, /_astro/phone-menu-bottom.CzDBd3Xa_ZtRGY1.jpg 800w, /_astro/phone-menu-bottom.CzDBd3Xa_ZGTHFY.jpg 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Phone sticky menu button on bottom" loading="lazy" decoding="async" fetchpriority="auto" width="980" height="613"> </picture> <p>There is a <a href="https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/" target="_blank" rel="noreferrer noopener">great article about this by Scott Hurff</a>. This article is a few years old, but it’s still applicable, maybe even more than when it released with phones getting bigger and bigger.</p> <p>However, there are a few more challenges that occur here.</p> <p><strong>1) We want our branding to be visible all the time</strong><br> This is a hard one for me to tackle. Personally, I know which website I’m visiting while scrolling through the content and I believe that there should be more interesting ways to make your brand pop inside of the page instead of using the logo. It’s not just a UX challenge here, it’s a design challenge. A very interesting challenge if you ask me…</p> <p><strong>2) We want people to return to an overview when entering on a detail page</strong><br> Maybe we can do something else: a sticky breadcrumb or a sticky return to overview? It might take less space than a logo.</p> <p><strong>3) What about tablets?</strong><br> Interesting question, because users with a tablet usually use their index finger to navigate around. I can see that placing the hamburger in the header might be a better idea there. There is also a lot more height, so it could remain sticky as well.</p> <p><strong>4) What about left-handed people?</strong> Aha, you got me there! Although the majority of people are right handed. When holding the phone with your left hand, it might be harder to reach a navigation button on the bottom right. Still, it’s easier than the top right.</p> <p>You could put it in the center, but then it might be the worst (or the best?) of both worlds. Which brings me to this crazy idea I had:</p> <h3 id="left-handed-media-queries">Left handed media queries.</h3> <p>We have preferences and media queries for so many things these days. In the settings of your device you might find something called <strong>dark mode.</strong> We can target this with CSS by using:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Targets user preference dark mode */</span> <span class="token punctuation">}</span> </code></pre> <p>So we are actually styling our website based on the user preference. That’s great!</p> <p>With accessibility (finally) getting more attention each day, why not introduce a left handed setting, we already do this when setting up our mouse.</p> <p>I actually did some research about this and noticed there is already <a href="https://github.com/w3c/csswg-drafts/issues/4443" target="_blank" rel="noreferrer noopener">an issue logged on the csswg-drafts</a>.</p> <p>I think this could be a great addition to CSS so I upvoted it! <strong>The suggested syntax is the following:</strong></p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token property">prefers-hand</span><span class="token punctuation">:</span> right</span> <span class="token punctuation">{</span> <span class="token selector">.mySuperCTA</span> <span class="token punctuation">{</span> <span class="token property">right</span><span class="token punctuation">:</span> auto<span class="token punctuation">;</span> <span class="token property">left</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>I agree that this isn’t a setting on mobile devices at the moment, but who knows, maybe we’ll see a marketing campaign soon “The first phone for lefties!”.</p> <h2 id="my-conclusion-on-sticky-headers">My conclusion on sticky headers.</h2> <p>I think there is a lot to think about here. It’s all very subjective and choices have to be made. Always think about your user first and what is beneficial. Do we need the sticky header? Or is it just to show off our logo? Do we want the menu at the top all the time, or do we want to show more content? I’m certainly not saying that sticky headers are a bad thing, they get misused and we probably should think about why we want them before we implement them as an easy solution.</p>Brecht De RuyteBootstrap & Tailwind: the current state of popular CSS librarieshttps://utilitybend.com/blog/bootstrap-tailwind-the-current-state-of-popular-css-libraries/https://utilitybend.com/blog/bootstrap-tailwind-the-current-state-of-popular-css-libraries/There was a time when I was absolutely against every type of CSS library. The thought of using pre-defined class names gave me the shivers. Now, after working with them for some time, here are some of the thoughts on the matter.Sun, 10 Oct 2021 00:00:00 GMT<picture> <source srcset="/_astro/bs-tw.CfzbiUVb_Z1U6soa.avif 375w, /_astro/bs-tw.CfzbiUVb_y7Cuw.avif 480w, /_astro/bs-tw.CfzbiUVb_ZhFo4B.avif 680w, /_astro/bs-tw.CfzbiUVb_Z2bmJn4.avif 800w, /_astro/bs-tw.CfzbiUVb_2laPDp.avif 980w, /_astro/bs-tw.CfzbiUVb_SkMzi.avif 1024w, /_astro/bs-tw.CfzbiUVb_2clNYz.avif 1660w, /_astro/bs-tw.CfzbiUVb_1bHUCu.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/bs-tw.CfzbiUVb_ZGyMYX.webp 375w, /_astro/bs-tw.CfzbiUVb_1LEhSI.webp 480w, /_astro/bs-tw.CfzbiUVb_UQgjA.webp 680w, /_astro/bs-tw.CfzbiUVb_ZWP4XR.webp 800w, /_astro/bs-tw.CfzbiUVb_Z1vtCLk.webp 980w, /_astro/bs-tw.CfzbiUVb_Z1TDOF5.webp 1024w, /_astro/bs-tw.CfzbiUVb_ZACNfN.webp 1660w, /_astro/bs-tw.CfzbiUVb_JhVIG.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/bs-tw.CfzbiUVb_Z1mYIFx.jpg" srcset="/_astro/bs-tw.CfzbiUVb_ZIVaIn.jpg 375w, /_astro/bs-tw.CfzbiUVb_1JhUaj.jpg 480w, /_astro/bs-tw.CfzbiUVb_StSAb.jpg 680w, /_astro/bs-tw.CfzbiUVb_Z10crHh.jpg 800w, /_astro/bs-tw.CfzbiUVb_Z1xQ0uJ.jpg 980w, /_astro/bs-tw.CfzbiUVb_1XaKz9.jpg 1024w, /_astro/bs-tw.CfzbiUVb_Z1N0lOv.jpg 1660w, /_astro/bs-tw.CfzbiUVb_ZVL6aC.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="Bootstrap &#38; Tailwind logo" loading="eager" decoding="async" fetchpriority="auto" width="2100" height="1181" class="img-fluid"> </picture><h2 id="bootstrap--tailwind-seem-to-be-all-the-craze">Bootstrap &amp; Tailwind seem to be all the craze.</h2> <p>Most of the time when using a framework and library, I usually worked with <a href="https://getbootstrap.com/" target="_blank" rel="noreferrer noopener">Bootstrap</a> or at least some variant of it. Even after working with a library for many years, I still believe that beautiful code, written from scratch, is more pleasing, exciting and can give your project that extra bit of “oomph” (I’ll get back to that). The second most popular one seems to be <a href="https://tailwindcss.com/" target="_blank" rel="noreferrer noopener">Tailwind</a>, which differentiates itself by being more “utility based”.</p> <p>Both of these libraries seem to have their ups and downs, and most importantly: they learn from each other. Bootstrap is gaining more utility classes and new “tailwind component websites” are fired into webspace every week or so.</p> <p>But let’s not forget, even though these are popular libraries, it doesn’t mean they are here to stay. A lot of frameworks and libraries seem to fade over time, but because of the popularity of these two, I’m pretty sure we’re good for a while. Material-UI is also one of the bigger ones but it doesn’t seem to get the big “hype” and usage as the other popular ones.</p> <h2 id="whats-good-about-css-libraries-in-general">What’s good about CSS libraries in general?</h2> <p>No matter how you look at it, one thing both of these have in common is a decent documentation. If you start a job in a company that uses one of these libraries, you could be a billable developer in no-time. From an economic point of view they are great because they’re easy to learn, fast and let’s be honest, clients really don’t care if you use a library or framework.</p> <p>These libraries have updates and some of them take too long (yes, I’m talking about Bootstrap 5). But the good thing about it is that many people work on these, whether it’s just giving feedback or active development. They are tested and they work when they get a major release.</p> <p>Another thing that works their magic are utility classes. I’ll be honest, I love these kinds of things as long as they’re not over-used. They make it easy to show a quick change inside the browser or to make a quick prototype.</p> <p>As a final thought I’d like to add that many good methods for writing CSS were created for them. Some of the styling ideas they have are just plain clever.</p> <h2 id="lets-talk-about-the-bad-things">Let’s talk about the bad things.</h2> <p>First of all, when you’re building a website using Bootstrap or Tailwind, you’re not actually writing CSS. Some people don’t have a clue what they’re doing, it just works and they’re happy. Yes, you will mostly add a bit of custom CSS to make the website a bit more unique but that’s still not the same as truly taking matters into your own hands. The libraries do get a bit outdated over time and if you like to use those awesome new CSS features, you’ll have to write them yourself anyway. In the end, you’re getting a bit lazy when using them all the time.</p> <p>One major issue I had when the first Bootstrap release was that I could see that something was made with that particular library without even inspecting the code. But thankfully that isn’t the case anymore. But from time to time, you still see a lot of websites that make you think: “yep, that’s Bootstrap”.</p> <p>Libraries include way too much CSS in your project. There are ways to counter that, for example, you could use <a href="https://purgecss.com/" target="_blank" rel="noreferrer noopener">PurgeCss</a> for production releases. In a world where performance matters, you should see every kb stripped from your files as a win no matter how hard you’re caching.</p> <h3 id="bootstrap-specific">Bootstrap specific.</h3> <p>Bootstrap has a lot of components and utilities, you are almost certain to never use even half of them. Even when you load the Sass files and start commenting out the component specific imports, it will still be too much. When you’re making use of the built-in components you’re probably making things too complicated as well. A very basic example is the following. Imagine, you just want a simple card with some text in it:</p> <picture> <source srcset="/_astro/simple-card.Cl97E-hE_ZM4RM0.avif 320w, /_astro/simple-card.Cl97E-hE_ZnMqfD.avif 480w, /_astro/simple-card.Cl97E-hE_1Sjy7i.avif 800w, /_astro/simple-card.Cl97E-hE_xGRKe.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/simple-card.Cl97E-hE_ZBtezw.webp 320w, /_astro/simple-card.Cl97E-hE_ZdbM3a.webp 480w, /_astro/simple-card.Cl97E-hE_23UcjL.webp 800w, /_astro/simple-card.Cl97E-hE_Q7Eh2.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/simple-card.Cl97E-hE_7A5ae.png" srcset="/_astro/simple-card.Cl97E-hE_1LiCMC.png 320w, /_astro/simple-card.Cl97E-hE_2aA5jY.png 480w, /_astro/simple-card.Cl97E-hE_ZCu471.png 800w, /_astro/simple-card.Cl97E-hE_Z1kwnep.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="Simple example of a HTML/CSS card" loading="lazy" decoding="async" fetchpriority="auto" width="490" height="51"> </picture> <p>In bootstrap, you might write something like this:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>card<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</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">&quot;</span>card-body<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> This is a simple card, i don&#39;t need a header or footer in it, but when using a framework, i&#39;m using way too much css for the simple thing i want to create... <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Which gives you the following output in your css:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.card</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">display</span><span class="token punctuation">:</span> -webkit-box<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> -ms-flexbox<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">-webkit-box-orient</span><span class="token punctuation">:</span> vertical<span class="token punctuation">;</span> <span class="token property">-webkit-box-direction</span><span class="token punctuation">:</span> normal<span class="token punctuation">;</span> <span class="token property">-ms-flex-direction</span><span class="token punctuation">:</span> column<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">min-width</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">word-wrap</span><span class="token punctuation">:</span> break-word<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-clip</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid <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.125<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card-body</span> <span class="token punctuation">{</span> <span class="token property">-webkit-box-flex</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token property">-ms-flex</span><span class="token punctuation">:</span> 1 1 auto<span class="token punctuation">;</span> <span class="token property">flex</span><span class="token punctuation">:</span> 1 1 auto<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>This is a lot of CSS for such a little bit of layout. Of course it is handy when you want to add a header later on, or an image, or a footer. It has a multi-purpose. But if you just need something like the example above. you can just write the following and it will save you a lot of output:</p> <pre class="language-html" data-language="html"><code is:raw="" 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">&quot;</span>card<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> This is a simple card, i don&#39;t need a header or footer in it, but when using a framework, i&#39;m using way too much css for the simple thing i want to create... <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span> </code></pre> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 1.25rem<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-clip</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token property">border</span><span class="token punctuation">:</span> 1px solid <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.125<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 0.25rem<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>You might be thinking that this doesn’t change all that much. Well, this little difference just <strong>removed 372 bytes</strong> from your CSS file-size (unminified for both). Not bad for a simple card, imagine how much more you could save.</p> <h3 id="tailwind-specific">Tailwind specific.</h3> <p>Tailwind is utility-based and personally, I like the idea of it. The problem is that many people just write HTML with so many utility class names that it becomes unreadable and hard to maintain. Tailwind suggests that if you have something repeatable that you write it in CSS instead but a lot of people seem to ignore that. Here is an example of a bad situation:</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><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">&quot;</span>h-12 px-6 m-2 text-lg text-indigo-100 transition-colors duration-150 bg-indigo-700 rounded-lg focus:shadow-outline hover:bg-indigo-800<span class="token punctuation">&quot;</span></span> <span class="token punctuation">&gt;</span></span> Large <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Seriously, this is just an overkill. Just write a general “.btn” class for your buttons so you can maintain them well in the future.</p> <h2 id="the-future-of-css">The future of CSS.</h2> <p>Let’s not forget that CSS specs are updated all the time. Frameworks and libraries have given us a lot to work with. Now, we can use variables in CSS, easy grids with the grid system, nesting CSS is starting to become a thing as is layering. It’s becoming such a strong language that can even <a href="https://utilitybend.com/blog/masonry-with-css-grid-finally-a-solution-without-javascript" target="_blank" rel="noreferrer noopener">handle masonry layouts</a> while in the past we had to use JavaScript. We have so much to choose from, we can make a battle plan for every type of layout we want to tackle and talk about our choices in a beautiful discussion. It’s great, you can’t do that when working with a library.</p> <p>There are so many things we can do now that aren’t available in the libraries. We have to make a fallback from time to time, but maybe we should ask the question: Is it better to “hack a library”, or to add a fallback and be future proof?</p> <h2 id="so-am-i-saying-css-frameworks-and-libraries-are-bad">So, am I saying CSS frameworks and libraries are bad?</h2> <p>When comparing this, I always picture a colouring book versus a sketch book. When working with a library, you’re actually just colouring a predefined drawing and maybe adding a little picture of a flower in it. While working from scratch, you’re making little pieces of art in your sketchbook, let’s call it, “working from sketch” from now on?</p> <p>I believe in libraries… As a corporate developer for many years, I know they work their magic. They can set up a project in no-time. Budgets get a bit lower because we’re colouring between the lines and you get told you did a great job using someone else’s code. We can be faster when using them and browser issues are less bound to happen. In the end, when working for a company, money and time are the big factors. When you’re from Belgium, you learn that good front-end development is a hard sell. It’s a shame but a simple truth.</p> <p>Even working on side projects makes me want to use a library from time to time. I have a kid, hobbies and friends. Choices in time have to be made for all of us.</p> <p>The biggest problem is that they provide too much comfort. Remember when you started out using CSS and how happy you were that something magically worked? You just knew you did something special and you loved it! You would go home with a smile on your face. Those feelings are much harder to find when you’re just using the library.</p> <p>So, use your libraries within your favourite framework but remember to try something different from time to time. Whether it’s a little codepen or a little project on the side. Because between all these libraries, frameworks and pre-defined sets there is a little thing that made us want to do this for a living, and it’s called CSS.</p>Brecht De RuyteMasonry with CSS Grid, finally a solution without JavaScripthttps://utilitybend.com/blog/masonry-with-css-grid-finally-a-solution-without-javascript/https://utilitybend.com/blog/masonry-with-css-grid-finally-a-solution-without-javascript/You might remember the days when Masonry grids were all the rage. Every website wanted to have the Pinterest-inspired layout. Well overdue, but there is something in the works at the W3C.Tue, 28 Sep 2021 00:00:00 GMT<picture> <source srcset="/_astro/masonry.CcDQnXbU_pBSIc.avif 375w, /_astro/masonry.CcDQnXbU_181GRc.avif 480w, /_astro/masonry.CcDQnXbU_Z1hXezc.avif 680w, /_astro/masonry.CcDQnXbU_cvFwR.avif 800w, /_astro/masonry.CcDQnXbU_18Calj.avif 980w, /_astro/masonry.CcDQnXbU_Z2tpBET.avif 1024w, /_astro/masonry.CcDQnXbU_1OXUE6.avif 1660w, /_astro/masonry.CcDQnXbU_Z2tiozm.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/masonry.CcDQnXbU_Z1N5aB.webp 375w, /_astro/masonry.CcDQnXbU_FAHXo.webp 480w, /_astro/masonry.CcDQnXbU_Z1Jodt0.webp 680w, /_astro/masonry.CcDQnXbU_ZeTilV.webp 800w, /_astro/masonry.CcDQnXbU_Gcbrv.webp 980w, /_astro/masonry.CcDQnXbU_Z1a1C40.webp 1024w, /_astro/masonry.CcDQnXbU_Z1UOdxV.webp 1660w, /_astro/masonry.CcDQnXbU_Z19ToXs.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/masonry.CcDQnXbU_1riIyC.jpg" srcset="/_astro/masonry.CcDQnXbU_Z1HR84U.jpg 375w, /_astro/masonry.CcDQnXbU_Z10sjUU.jpg 480w, /_astro/masonry.CcDQnXbU_1DIRqC.jpg 680w, /_astro/masonry.CcDQnXbU_Z1UXlgf.jpg 800w, /_astro/masonry.CcDQnXbU_ZYQQrN.jpg 980w, /_astro/masonry.CcDQnXbU_2o6tOC.jpg 1024w, /_astro/masonry.CcDQnXbU_1CiSkG.jpg 1660w, /_astro/masonry.CcDQnXbU_2odGUa.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="A masonry grid with CSS" loading="eager" decoding="async" fetchpriority="auto" width="3572" height="1841" class="img-fluid"> </picture> <div class="alert alert-warning"><p><strong>Attention:</strong> This article, dated 2021, presents information from its experimental phase. The masonry feature has garnered increased attention of late. I intend to produce a fresh article once the trajectory of this CSS feature becomes definitive. My aim is to ensure readers avoid outdated information. In May 2023, consider the following articles for updated insights:</p><ul><li><a href="https://developer.chrome.com/blog/masonry" target="_blank" rel="noreferrer noopener">Proposal by Chrome</a></li><li><a href="https://webkit.org/blog/15269/help-us-invent-masonry-layouts-for-css-grid-level-3/" target="_blank" rel="noreferrer noopener">Proposal by Webkit</a></li></ul><p>For now, this article is kept the same as originated for historic reasons:</p></div> <p>Recently, I read this <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/" target="_blank" rel="noreferrer noopener">article by Rachel Andrew</a>. When I see that Rachel posts something about new CSS features, I tend to bookmark the article so I can read it later with my full attention. If you haven’t heard of her, I strongly encourage you to do some research as she is one of the best speakers when it comes to everything HTML and CSS.</p> <p>I’m not going to copy / paste her article on my blog. But when reading it, I couldn’t wait to try out the CSS Grid Masonry feature. Here are some of the things I tried and some thoughts that came to mind. I encourage you to read her article nevertheless.</p> <h2 id="masonry-is-that-still-a-thing">Masonry, is that still a thing?</h2> <p>The term “Masonry” probably came from the <a href="https://masonry.desandro.com/" target="_blank" rel="noreferrer noopener">o-so-popular jQuery plugin</a> that I worked with in the early days. It has come a long way since then with native JS support and a lot of alternative libraries to choose from. I’m truly happy that they chose the name “Masonry” for this CSS based version as well. The name has become something so defining that we use it to describe this type of layout, no matter which library is used.</p> <p>Let’s be honest here. Although the Masonry layout with CSS is an amazing feature, it comes a little too late. The huge hype isn’t what it used to be anymore, a lot of designers have the “it’s been done” feeling when designing a grid.</p> <p>But who knows, there are a lot of things that come back like 80’s music had a revival in the 2000’s, so I’m sure that we might get a Masonry revival if we don’t need to load a big JavaScript library for it. And really, it’s easy.</p> <h2 id="support-for-masonry-in-css-only">Support for Masonry in CSS-only.</h2> <p>For now, we’ll have to try it out with <a href="https://www.mozilla.org/en-US/firefox/channel/desktop/" target="_blank" rel="noreferrer noopener">Firefox nightly</a> with the <code>layout.css.grid-template-masonry-value.enabled</code> flag enabled. To find out if it’s enabled go to <code>about:config</code> in the browser. All installed? Flag enabled? Ok, let’s go and see how to achieve this.</p> <h2 id="how-to-get-a-masonry-grid-in-css">How to get a Masonry grid in CSS?</h2> <p>Let’s add some basic HTML for our grid.</p> <pre class="language-html" data-language="html"><code is:raw="" class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>grid<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>text-box<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>Just a test with firefox nightly<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras convallis sodales erat vel accumsan. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/800/600/<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/800/600/<span class="token punctuation">&quot;</span></span> <span class="token attr-name">alt&quot;&quot;</span> <span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/900/600<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/800/600/<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/800/600/<span class="token punctuation">&quot;</span></span> <span class="token attr-name">alt&quot;&quot;</span> <span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>figure</span><span class="token punctuation">&gt;</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">&quot;</span>https://unsplash.it/900/600<span class="token punctuation">&quot;</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">&quot;</span><span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>figure</span><span class="token punctuation">&gt;</span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">&gt;</span></span> </code></pre> <p>Now let’s add the CSS.</p> <pre class="language-css" data-language="css"><code is:raw="" 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">gap</span><span class="token punctuation">:</span> 5px<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 property">grid-template-rows</span><span class="token punctuation">:</span> masonry<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figure</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">display</span><span class="token punctuation">:</span> block<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 punctuation">}</span> <span class="token selector">figure &gt; 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">max-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>There it is, a 3 column CSS Masonry grid, now isn’t that easy? :)</p> <h2 id="playing-around-with-css-grid-order">Playing around with CSS Grid order.</h2> <p>One of the things that always was a hassle when using the Masonry JavaScript was changing the order based on a viewport. So naturally, this was one of the first things I wanted to try out. I added a bit of text at the start of my grid to have it at the top of my screen when watching on mobile. However, when viewing it on a large viewport, I wanted the text to sit nicely in the middle. So I tried the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">figure:first-of-type</span> <span class="token punctuation">{</span> <span class="token property">order</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.text-box</span> <span class="token punctuation">{</span> <span class="token property">order</span><span class="token punctuation">:</span> 2<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">figure</span> <span class="token punctuation">{</span> <span class="token property">order</span><span class="token punctuation">:</span> 3<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>It worked as expected and I love it!</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/ZEymZoY?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="adding-some-grid-column-spans">Adding some grid-column spans.</h2> <p>This is where it got a bit more tricky. I wanted to highlight some images by making them 2/3th of the size inside the grid. Doing that works like a charm but for some reason I wanted the grid to nicely close the items together and make it so that the masonry always gets a good fit. Of course this might be taking things too far.</p> <picture> <source srcset="/_astro/masonry-broken-grid.B7rpajUu_Z1ew0O8.avif 320w, /_astro/masonry-broken-grid.B7rpajUu_zfwHL.avif 480w, /_astro/masonry-broken-grid.B7rpajUu_xrWs6.avif 800w, /_astro/masonry-broken-grid.B7rpajUu_Xub5S.avif 980w" type="image/avif" sizes="(max-width: 800px) 100vw, 80vw"><source srcset="/_astro/masonry-broken-grid.B7rpajUu_Z1snsfc.webp 320w, /_astro/masonry-broken-grid.B7rpajUu_lo5hH.webp 480w, /_astro/masonry-broken-grid.B7rpajUu_jAv22.webp 800w, /_astro/masonry-broken-grid.B7rpajUu_JCIEO.webp 980w" type="image/webp" sizes="(max-width: 800px) 100vw, 80vw"> <img src="https://utilitybend.com/_astro/masonry-broken-grid.B7rpajUu_e7gsx.png" srcset="/_astro/masonry-broken-grid.B7rpajUu_Z29ycSz.png 320w, /_astro/masonry-broken-grid.B7rpajUu_ZkLElF.png 480w, /_astro/masonry-broken-grid.B7rpajUu_ZmzeBl.png 800w, /_astro/masonry-broken-grid.B7rpajUu_3rY1r.png 980w" sizes="(max-width: 800px) 100vw, 80vw" alt="A masonry example with gaps in it" loading="lazy" decoding="async" fetchpriority="auto" width="16" height="9"> </picture> <p>I created a lot of gaps this way</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/VwWqrBY?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h3 id="just-an-idea-to-make-the-items-fit">Just an idea to make the items fit.</h3> <p>Maybe we could have a property that makes it so that the order isn’t necessarily respected, but it respects the gutter instead. I know this might be taking things a little too far, but imagine how cool it would be if we could do something like this:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"><span class="token selector">.grid</span> <span class="token punctuation">{</span> <span class="token property">grid-template-rows</span><span class="token punctuation">:</span> masonry-fit<span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <h2 id="its-nice-to-make-the-grid-work-with-every-type-of-content">It’s nice to make the grid work with every type of content.</h2> <p>As sort of a final little test, I wanted to try it out with different cards, sort of like a news overview and add different sizes of images. We all have been there… Getting content by a third-party API where you have no control whatsoever.</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/xxrmYqX?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="using-it-now-with-a-fallback-supports">Using it now with a fallback (@supports).</h2> <p>You can actually use it right now in any browser if you add a decent fallback. It’s a little bit of a hassle but it will pay off when more browsers start to support it. I might update my own <a href="https://utilitybend.com/photography" target="_blank" rel="noreferrer noopener">photography pages</a> as well to have decent support with fallback later on.</p> <p>You can test support by putting this in your CSS or JavaScript with the following:</p> <pre class="language-css" data-language="css"><code is:raw="" class="language-css"> <span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token property">grid-template-rows</span><span class="token punctuation">:</span> masonry<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token comment">/* Masonry content here */</span> <span class="token punctuation">}</span> const supportGrid = CSS.<span class="token function">supports</span><span class="token punctuation">(</span><span class="token string">&quot;grid-template-rows&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;masonry&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token selector">if (!supportGrid)</span> <span class="token punctuation">{</span> //add the js fallback here <span class="token punctuation">}</span> </code></pre> <p>I added a small example of this on my codepen, I loaded Masonry with jQuery and only initialised it when CSS Masonry is not supported. Would be even better if you didn’t include the libraries in that case. You can optimise this a lot better.</p> <div class="embed"><iframe src="https://codepen.io/utilitybend/embed/preview/rNwoEKZ?default-tab=result&editable=true" frameborder="0" allowtransparency="true" style=""></iframe></div> <h2 id="conclusion-and-concerns">Conclusion and concerns.</h2> <p>Just as Rachel describes in her article: Masonry is a great tool to visualise content but it should be handled with care, especially when it comes to accessibility and screen readers. When we start changing orders, things might get messy so tread carefully. That aside, I think this type of layout is still beautiful for photography / design portfolios. It might not be the best choice for news overviews, but that’s up to you.</p> <p>Another concern I have is browser differences when it starts to roll out and partial support. Even though websites do not need to look exactly the same in every browser, it’s still hard to explain to a client why image A is in another location (yes, I’m looking at you Safari…).</p> <p>I think this feature might bring a revival of the Masonry layouts, I can only hope they get implemented with correct accessibility and don’t get over-used. Because in the end: <strong>there is nothing wrong with a basic beautiful grid.</strong></p> <p>I haven’t found any issues with the implementation so far, but if I find any…</p> <blockquote> <p>If you run into problems or can’t do something you were able to do in your previous implementation, please let the CSSWG know by <a href="https://github.com/w3c/csswg-drafts/issues/" target="_blank" rel="noreferrer noopener">raising an issue</a>.</p> </blockquote>Brecht De RuyteAnother one enters the webspace!https://utilitybend.com/blog/another-one-enters-the-webspace/https://utilitybend.com/blog/another-one-enters-the-webspace/So, here it is, my new blog. It took me a bit longer than I expected, but it was worth the journey. I can’t wait to fill this up with content that might be beneficial for others, or with things that I just want to share in general. So here are a few things you can expect…Thu, 16 Sep 2021 00:00:00 GMT<picture> <source srcset="/_astro/brecht-de-ruyte.mN1VlMkI_Z2wjw8y.avif 375w, /_astro/brecht-de-ruyte.mN1VlMkI_2jbUFc.avif 480w, /_astro/brecht-de-ruyte.mN1VlMkI_27yYHD.avif 680w, /_astro/brecht-de-ruyte.mN1VlMkI_Xpbmk.avif 800w, /_astro/brecht-de-ruyte.mN1VlMkI_1P8AgN.avif 980w, /_astro/brecht-de-ruyte.mN1VlMkI_ZCl6iM.avif 1024w, /_astro/brecht-de-ruyte.mN1VlMkI_1hJ7Ls.avif 1660w, /_astro/brecht-de-ruyte.mN1VlMkI_1X2oH.avif 1800w" type="image/avif" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"><source srcset="/_astro/brecht-de-ruyte.mN1VlMkI_Z1CekFr.webp 375w, /_astro/brecht-de-ruyte.mN1VlMkI_Z1QU1FC.webp 480w, /_astro/brecht-de-ruyte.mN1VlMkI_Z23wWDb.webp 680w, /_astro/brecht-de-ruyte.mN1VlMkI_1RumOr.webp 800w, /_astro/brecht-de-ruyte.mN1VlMkI_Z2kXm51.webp 980w, /_astro/brecht-de-ruyte.mN1VlMkI_Z2nEgmi.webp 1024w, /_astro/brecht-de-ruyte.mN1VlMkI_Zsz2h3.webp 1660w, /_astro/brecht-de-ruyte.mN1VlMkI_Z1Il7DN.webp 1800w" type="image/webp" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw"> <img src="https://utilitybend.com/_astro/brecht-de-ruyte.mN1VlMkI_Z1oaRrn.jpg" srcset="/_astro/brecht-de-ruyte.mN1VlMkI_1jy3zY.jpg 375w, /_astro/brecht-de-ruyte.mN1VlMkI_14RmzN.jpg 480w, /_astro/brecht-de-ruyte.mN1VlMkI_SfqCf.jpg 680w, /_astro/brecht-de-ruyte.mN1VlMkI_ZfTmI4.jpg 800w, /_astro/brecht-de-ruyte.mN1VlMkI_AO2bp.jpg 980w, /_astro/brecht-de-ruyte.mN1VlMkI_Z2n5rxa.jpg 1024w, /_astro/brecht-de-ruyte.mN1VlMkI_Zs0drU.jpg 1660w, /_astro/brecht-de-ruyte.mN1VlMkI_Z1HLiOF.jpg 1800w" sizes="(max-width: 575px) 100vw,(max-width: 767px) 100vw,(max-width: 991px) 100vw,80vw" alt="me, Brecht De Ruyte" loading="eager" decoding="async" fetchpriority="auto" width="2000" height="2000" class="img-fluid"> </picture><h2 id="who-are-you-what-do-you-do">Who are you, what do you do?</h2> <p>My name is Brecht, I’m a front-end developer living in Belgium with a wide interest in the “internet of things”. At the moment I work for an international company: <a href="https://www.iodigital.com/">iO</a>.</p> <h2 id="so-why-did-you-start-this-blog">So why did you start this blog?</h2> <p>Many reasons! The most important one is sharing some of the things I learn along the way. I love to explore outside of the typical “front-end world” and touch topics such as UX, marketing, work methods and many more.</p> <p>Don’t get me wrong though, it will mostly be front-end related. But don’t judge me if I write something about other program languages or an awesome new game I tried along the way.</p> <h2 id="why-write-in-english-even-though-youre-not-a-native-speaker">Why write in English even though you’re not a native speaker?</h2> <p>There are two main reasons for this:</p> <ul> <li>A bigger audience.</li> <li>As practice, because languages get rusty over time.</li> </ul> <p>So, I might make a (few) mistake(s) from time to time. But practice makes perfect. I’m a strong believer of that.</p> <h2 id="what-will-you-do-differently-than-thousands-of-other-blogs">What will you do differently than thousands of other blogs?</h2> <p>Honestly, not much. I’m a pretty normal guy. One thing that I might do differently is the subject of developer happiness. We live in a world, where everything is rushed, clients expect more and they want it fast. How can we stay sane in a fast world, combined with studying new technologies, staying healthy, bringing your kid to school and finding some “me-time” in the middle of it.</p> <h2 id="you-mention-a-kid-do-you-have-kids">You mention a kid, do you have kids?</h2> <p>I have a wonderful healthy 2-year-old daughter who I love unconditionally. Her name is Eva and her favourite words at the moment are: “Watch Frozen!”, “I’ll do it!” and “no!”. She can be a handful at times, but all the laughs and giggles make up for that in no-time. She’s the joy of my life, as is her mother, my wife Renée.</p> <h2 id="what-do-you-want-to-achieve-with-your-blog">What do you want to achieve with your blog?</h2> <p>If I could help out a handful of people, I would be more than happy. I’m not using some crazy marketing strategy for this, nor am I planning to put my pages full of ads. The only tracking tool is my analytics. You might notice that I didn’t add a comment section. It’s not because I don’t want to interact, but there are other channels where people can contact me. Please do, happy to help or maybe start a little brainstorm or healthy discussion.</p> <h2 id="any-special-hobbies">Any special hobbies?</h2> <p>Absolutely, I’m a strong believer in a good work-life balance. My main hobbies are card- and board games, nature photography and I play a bit of guitar as well. I added <a href="https://utilitybend.com/photography">some pages with photography</a> on my website if you want to view them. When I’m alone in a beautiful landscape, time seems to stop for a moment, it’s great therapy after a busy week. The images aren’t the main thing of this website and I’m not a “professional photographer”, but they’re a part of my identity so they deserved a place here.</p> <h2 id="and-the-final-question-why-the-name-utilitybend">And the final question, why the name “utilitybend”?</h2> <p>When I post something on the web, I usually take on another name. The main reason is that my name is typical Flemish and it’s hard for people to pronounce or remember.</p> <p>The name utilitybend was just an idea that grew over time. When I started as a front-end developer, people referred to my job as “pixel bending”. These days, we aren’t really using pixels for everything, however, we are using more utility classnames in HTML/CSS than ever before. So I guess a lot of us are bending utilities now.</p> <blockquote> <p>As a final word, I do hope my future articles can help you out a bit. Cheers!</p> </blockquote>Brecht De Ruyte