CSS and design systems people - BlogFlock 2025-03-15T14:42:24.032Z BlogFlock Michelle Barker, Hidde de Vries, Robin Rendle, Stephanie Eckles, Sara Joy, Sara Soueidan, Robin Rendle, Adam Argyle Let the stagger experiments begin - Adam Argyle https://nerdy.dev/sibling-index?utm_source=rss 2025-03-11T00:08:03.000Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/sibling-index-stagger-effect.png" alt="ul > li { --stagger: calc((sibling-index() - 1) * .1s); transition: opacity 1s var(--ease-3) var(--stagger), translate 1s var(--ease-spring-3) var(--stagger); } " height="758" width="1272" /> <p>2019, <a href="https://github.com/w3c/csswg-drafts/issues/4559">I proposed an idea</a> to help remove a ton of boilerplate code from CSS and HTML while also making stagger effects easier on authors. The proposal ended up being <a href="https://drafts.csswg.org/css-values-5/#tree-counting"><code>sibling-index()</code></a> and <a href="https://drafts.csswg.org/css-values-5/#tree-counting"><code>sibling-count()</code></a>.</p> <p>Fun <a href="https://chromestatus.com/feature/6225478530367488?gate=5089301572091904">news</a>! <strong>You can try it in <a href="https://www.google.com/chrome/canary/">Canary</a> today</strong> and I&#39;ve got this little example to help kick off some ideas for you!</p> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/sibling-index-demo.mp4 width="1224" height="918" alt="" preload="none" poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/sibling-index-demo.jpg" controls loop muted playsinline allowFullScreen /></p> <p><a href="https://codepen.io/argyleink/pen/KwKXPYW">Try on Codepen</a></p> <h2> Staggering animation <a name="staggering-animation" href="#staggering-animation">#</a> </h2> <p>Instead of inlining a CSS variable for each element:</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> class</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"parent"</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-count: 5"</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-index: 1"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-index: 2"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-index: 3"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-index: 4"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> style</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"--sibling-index: 5"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <p>You can use <code>sibling-index()</code> to retrieve it just in time instead of doing the work in the template or JS.</p> <p>Clean that HTML up to make room for <code>sibling-index()</code> by removing all the inline styles:</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-token-function)"> class</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"parent"</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <p>and then drop in this new magic bomb:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.parent</span><span style="color:var(--shiki-token-keyword)"> ></span><span style="color:var(--shiki-token-string-expression)"> *</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> opacity 0.3</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> ease</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition-delay</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> calc</span><span style="color:var(--shiki-token-constant)">(sibling-index() * 100</span><span style="color:var(--shiki-token-keyword)">ms</span><span style="color:var(--shiki-foreground)">);</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>and now you have a <code>100ms</code> staggered crossfade transition effect šŸ¤“</p> <h2> Staggering color <a name="staggering-color" href="#staggering-color">#</a> </h2> <p>There&#39;s nothing stopping you from using <code>sibling-index()</code> to stagger a color change, or anything else!</p> <p>Here the hue increments by 50 each element.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.parent</span><span style="color:var(--shiki-token-keyword)"> ></span><span style="color:var(--shiki-token-string-expression)"> *</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --hue</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> calc</span><span style="color:var(--shiki-token-constant)">(sibling-index() * 50</span><span style="color:var(--shiki-foreground)">);</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> oklch</span><span style="color:var(--shiki-token-constant)">(70</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-constant)"> 70</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--hue))</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>You could easily add a <code>min()</code> or <code>clamp()</code> or trig functions to help you too.</p> <h2> Time to experiment <a name="time-to-experiment" href="#time-to-experiment">#</a> </h2> <p>Share with me what you build! <a href="https://codepen.io/argyleink/pen/KwKXPYW">Fork this Codepen</a></p> Abandon all keyboards - Robin Rendle https://robinrendle.com/notes/abandon-all-keyboards/ 2025-03-11T00:02:06.000Z <p>Itā€™s obnoxiously beautiful right now.</p> <p>Itā€™s a cracking, 10/10 stunner of a California sunset and itā€™s pouring in through the windows of this empty cafe as I click-clack on my infinite click-clack machine. Itā€™s as if the whole world has become a kaleidoscope of West Coast reds and oranges just for me. But, alas, I donā€™t feel much of anything. I am, in fact, this: a total and absolute nothing inside. Familiar feelings of course, nothing scary at all, itā€™s just plain olā€™ burnout. There was a rush of work over a few monthsā€”go, go, go! build, build, build!ā€”and then: something clicked and I was done. No more gas in the tank. No vacancies at the inn. All spoons gone.</p> <p>The problem with these feelings is that they come out of nowhere and I ignore the symptoms until they bleed into other parts of my life. But if I canā€™t care for writing or reading or code or design or anything fun and exciting then what even am I? Enthusiasm is my middle name! I was born to point at things excitedly! And yet in the evenings now Iā€™ll watch a great movie or a TV show or play a game where ten thousand people poured years of their lives into it and ā€” nothing.</p> <p>It all just kinda washes over me.</p> <p>When this sort of thing happens I know what I need: abandon all keyboards! Discard all screens! Throw myself into a stack of books and hurl my charmless corpse across a foreign city so that my empathy and hope refills itself. But right now itā€™s tough to be optimistic about that because today nothing matters. Work? Eh. Friends? Bleurgh. Going outside? Taking care of myself? Planning things? Bleurgh.</p> <p>Bleurgh to all things!</p> <p>In moments like this thereā€™s a shift in me towards mean and unpleasant thoughts, a kind of cynicism thatā€™s impossible to shake. I see the worst in people, almost as if Iā€™m hoping for the worst things to happen. But that cruelty tends to become laser-focused on me. Like the other day, when I woke up and my very first thought to start the day was: ā€œchrist, Iā€™m such a bad designer.ā€</p> <p>Yikes! What the hell, brain! Also: please pick something more cool and interesting to be sad about at the very least! I get that deep down you crave being interesting and sad but this is the least interesting way to sad! Try harder!</p> <p>Anyway, Iā€™ve booked two weeks off work. Iā€™ll plan something, go somewhere, write something. Iā€™ll figure it out.</p> <p>But, until then: bleurgh.</p> Views on views - Hidde's blog https://hidde.blog/views/ 2025-02-28T16:13:12.240Z <p>If WCAG 3 would adopt the word ā€œviewā€ instead of ā€œweb pageā€, the standard would be more useful in non-web contexts like native apps, documents and <a href="https://www.w3.org/TR/xaur/">XR</a>. Because many wish to understand how accessible such environments are, we should consider adopting a unit that isnā€™t ā€œweb pageā€. To me, ā€œviewā€ is the strongest contender.</p> <p><em>Context: as part of the Accessibility Guidelines Working Group (AGWG) work on WCAG 3, I'm leading a subgroup on (re)defining ā€œviewsā€ for WCAG 3. This group builds further on <a href="https://www.w3.org/2024/Talks/TPAC/breakouts/defining-views/">a discussion at TPAC 2024</a>, which builds on discussions over many years about WCAG and WCAG2ICT. Opinions in this post do not reflect those of the subgroup, or of my clients.</em></p> <p>I would love any feedback in <a href="https://github.com/w3c/wcag3/discussions/286">wcag3/discussions/286</a>.</p> <h2>The web page</h2> <p>In WCAG, and most WCAG-related documents, the notion of a ā€œweb pageā€ does quite a lot of work: it's in the name of some success criteria and it's WCAG 2's ā€œunit of conformanceā€, i.e. the thing people can claim conformance about.</p> <p>A <a href="https://www.w3.org/TR/WCAG22/#dfn-web-page-s">web page, in WCAG 2</a>, is unambiguously scoped to something to obtain from a single URI:</p> <blockquote> <p>a non-embedded resource obtained from a single URI using HTTP plus any other resources that are used in the rendering or intended to be rendered together with it by a user agent</p> </blockquote> <p>It's so unambiguous that we'd struggle to come up with examples where one person would draw the boundary between two web pages differently than the next. Well done, team WCAG!</p> <p>Inevitably, though, a unit of conformance that can apply more broadly will be more ambiguous.</p> <h3>In other places</h3> <p>No less than 16 success criteria in <a href="https://www.w3.org/TR/WCAG22">WCAG 2.2</a> mention web pages: <a href="https://www.w3.org/TR/WCAG22/#audio-control">1.4.2 Audio control</a>, <a href="https://www.w3.org/TR/WCAG22/#no-keyboard-trap">2.1.2 No keyboard trap</a>, <a href="https://www.w3.org/TR/WCAG22/#three-flashes-or-below-threshold">2.3.1 Three flashes or below treshold</a>, <a href="https://www.w3.org/TR/WCAG22/#three-flashes">2.3.2 Three flashes</a>, <a href="https://www.w3.org/TR/WCAG22/#bypass-blocks">2.4.1 Bypass blocks</a>, <a href="https://www.w3.org/TR/WCAG22/#page-titled">2.4.2 Page titled</a>, <a href="https://www.w3.org/TR/WCAG22/#focus-order">2.4.3 Focus order</a>, <a href="https://www.w3.org/TR/WCAG22/#multiple-ways">2.4.5 Multiple ways</a>, <a href="https://www.w3.org/TR/WCAG22/#location">2.4.8 Location</a>, <a href="https://www.w3.org/TR/WCAG22/#location">2.5.6 Concurrent input mechanisms</a>, <a href="https://www.w3.org/TR/WCAG22/#language-of-page">3.1.1 Language of page</a>, <a href="https://www.w3.org/TR/WCAG22/#consistent-navigation">3.2.3 Consistent navigation</a>, <a href="https://www.w3.org/TR/WCAG22/#consistent-identification">3.2.4 Consistent identification</a>, <a href="https://www.w3.org/TR/WCAG22/#consistent-help">3.2.6 Consistent help</a>, <a href="https://www.w3.org/TR/WCAG22/#error-prevention-legal-financial-data">3.3.4 Error prevention (legal, financial data)</a> and <a href="https://www.w3.org/TR/WCAG22/#error-prevention-all">3.3.6 Error prevention (all)</a>.</p> <p>It's also used in documents like <a href="https://www.w3.org/TR/WCAG-EM/">WCAG-EM</a>, the evaluation method.</p> <h3>Conformance</h3> <p>ā€œWeb pageā€ is WCAG 2's ā€œunit of conformanceā€, meaning that if you want to say something ā€œconformsā€ to WCAG, that <em>something</em> should be a web page, and not something else, like a date picker component that you've built. You can use WCAG to reason about the accessibility of that date picker component, but a claim that it conforms to WCAG would not make sense.</p> <p>Note, the phrase ā€œunit of conformanceā€ isn't actually mentioned or defined in WCAG itself, but WCAG2ICT explains it clearly:</p> <blockquote> <p>(ā€¦) the unit of conformance in WCAG 2 is a single web page (ā€¦)</p> </blockquote> <p>(From <a href="https://www.w3.org/TR/wcag2ict-22/#interpretation-of-web-terminology-in-a-non-web-context">Interpretation of Web Terminology in a Non-web Context</a>)</p> <p>Conformance claims are also specifically <a href="https://www.w3.org/TR/WCAG22/#cc2">about full web pages</a>, not parts of them. They can be about more than a single page, though:</p> <blockquote> <p>a conformance claim may be made to cover one page, a series of pages, or multiple related web pages.</p> </blockquote> <p>(From: WCAG 2.2, <a href="https://www.w3.org/TR/WCAG22/#conformance-claims">section 5.3: conformance claims</a>)</p> <p>That's common too: in fact, accessibility conformance reports usually cover a whole website.</p> <h2>Views</h2> <p>ā€œViewsā€ could be a new unit to use, meaning web pages on websites and other things in non-web contexts. View are not defined in WCAG 2 (they are <a href="https://www.w3.org/TR/UAAG20/#v">in UAAG</a> and <a href="https://www.w3.org/TR/ATAG20/#def-View">in ATAG</a>, interestingly, and a <a href="https://www.w3.org/1999/05/WCA-terms/#Page">1999 doc defines ā€œpage viewā€</a>). The <a href="https://www.w3.org/TR/wcag3/">latest WCAG 3 draft</a> also has a definition for view, we'll get to that later.</p> <p>Views as a unit of conformance for WCAG have been considered before, most nobably in the WCAG2ICT groups, see for instance the discussion in reply to the concept of <a href="https://lists.w3.org/Archives/Public/public-wcag2ict-tf/2012Jul/0038.html">User Interface Context</a>. In all those years, the group could not think of a definition that wouldnā€™t introduce problems of its own.</p> <p>Has anything really changed since then? Well, one thing that is different now, is that we have more variety in products that need accessibility conformance evaluated than ever. There seems to be a growing desire to bring our units of conformance up to date with that reality. It would make conformance assessments more practical to their writers and readers and simplify monitoring by governments.</p> <p>I feel the balance between theoretical purity and practical need has shifted.</p> <h3>Requirements for a new definition</h3> <p>When we set off thinking about views, we discussed requirements, including that we want ā€œviewsā€ to:</p> <ul> <li>Not confuse people who have an existing understanding of the word ā€˜viewā€™, which is a word used by many, including developers (like <a href="https://reactnative.dev/docs/view">views in React Native</a>, <a href="https://docs.djangoproject.com/en/5.1/topics/http/views/">views in Django</a>, views that <a href="https://www.w3.org/TR/css-view-transitions-1/">CSS view transitions</a> transition and <a href="https://dev.mysql.com/doc/refman/8.4/en/views.html">views in MySQL</a>)</li> <li>Not reduce accessibility requirements: a new definition shouldn't cause things to meet WCAG that don't today.</li> <li>ā€˜Workā€™ for a variety of different contexts, like web pages, (native) applications, VR/AR and documents (I know, a long wishlist)</li> <li>Useful for people who do conformance assessments (accessibility specialists/consultants), or use them in some way (designers, developers, monitoring agencies, judges in some areas).</li> </ul> <h3>Steve Faulkner's suggestion</h3> <p>As mentioned, there is an existing draft definition in the <a href="https://www.w3.org/TR/wcag3/#dfn-view">latest WCAG 3 draft</a>. I agree with Steve Faulker <a href="https://html5accessibility.com/stuff/2025/02/24/wcag-3-a-change-of-context/">that it is wooly</a>.</p> <p>What the exact definition needs to be is yet to be determined, but after the initial (less-wooly) definition we presented to the Working Group, Steve proposed to base the definition around ā€œchange of contextā€.</p> <p>Steve's proposed definition:</p> <blockquote> <p>Conformance scope that includes all content visually and programmatically available without a change of context</p> </blockquote> <p>In addition, he suggests updating ā€œchange of contextā€ to be:</p> <blockquote> <p>Major change in the content of the view that, if made without user awareness, can disorient users who are not able to view the entire view simultaneously</p> <p>Changes in context include changes of:</p> <ul> <li>user agent;</li> <li>viewport;</li> <li>content that changes the meaning of the view.</li> </ul> <p>A change of content is not always a change of context. Changes in content, such as an expanding outline, dynamic menu, opening a non-modal dialog, or a tab control do not necessarily change the context.</p> <p>Example: Opening a new window, opening a modal dialog, going to a new view (including anything that would look to a user as if they had moved to a new view) or significantly re-arranging the content of a view are examples of changes of context.</p> </blockquote> <p>I like where that is going.</p> <h2>Subviews?</h2> <p>Sometimes it makes sense to talk about specific parts of a UI or view, like a modal overlay, drawer or full screen search functionality. We looked at a number of examples within the group and found we were internally conflicted about some cases, there was a lot of gray area.</p> <p>For this reason, I think we should in fact <em>not</em> define ā€œsubviewā€, contrary to what we intitially thought. I'd love to know what others think, please add your thoughts to the <a href="https://github.com/w3c/wcag3/discussions/286#discussioncomment-12255792">subview part of the discussion</a>.</p> <h2>Possible issues</h2> <p>There are some disadvantages to ā€œviewā€, and we got some excellent feedback from the Working Group in our initial discussion. You can see all in the <a href="https://www.w3.org/2025/02/18-ag-minutes.html">minutes of 18 February</a>, but I'll highlight two here.</p> <h3>More subjectivity</h3> <p>Among the feedback we got from the Accessibility Guidelines Working Group, was Gregg Vanderheiden's feedback that I'll summarise as: ā€œweb pageā€œ is objective and ā€œviewā€ is not.</p> <p>I think he's technically correct that we lose some objectivity. But I don't think that's as much of a change to the status quo. Accessibility practioners define scopes for tests today already, and, if we're honest, subjectivity already exists in accessibility conformance reports. Bad actors can act bad today, that's not a reason to avoid making things more usable for all actors. Especially if a new term would help with clarity about accessibility or inaccessibility of products.</p> <p>Besides, the cat is out of the bag already: companies and governments need other environments than web apps assessed, and whether they do that with an unambiguous but inapplicable definition for ā€œweb pageā€ or with a broader definition for ā€œviewā€ is unlikely to make the difference.</p> <h3>In app development</h3> <p>In native app development, a world Iā€™ll admit Iā€™m not very familiar with, the word ā€œviewā€ seems to refer not to a whole screen or page, but to parts of it, as I understand similar to what on websites, youā€™d call components. I learned this when I presented some of these ideas in the Mobile Accessibility Task Force (MATF), where ā€œpageā€ was offered as an alternative to ā€œweb pageā€ that could work as a unit of conformance for (native) apps.</p> <p>This may get into ā€œconfusing people with existing definitions of viewā€ territory, I am not sure what's the best way out, I worry ā€œpageā€ could introduce similar problems in terms of future proofing. A potential solution, from Jan Jaap de Groot, could be that a different word than view is used in a document like WCAG2Mobile, which then points at WCAG's ā€œviewā€ for the definition. Others I talked to suggested something similar, we could have different units of conformance for different contexts, that all point to one broader unit.</p> <h3>SC meaning</h3> <p>One issue that we didn't get to much, but that is essential: what about all the SCs that mention web pages today? The published note <a href="https://www.w3.org/TR/wcag2ict-22">WCAG2ICT</a> covers mappings from ā€œnon-web software and documentsā€ to WCAG 2 success criteria and EN 301 549 (clause 10 and 11) harmonises with that. Mapping is not be in the scope for the task of defining ā€œviewsā€ per se, but it deserves our attention to consider what requirements would look like if we used a word like view.</p> <h2>Feedback wanted</h2> <p>In this post, I've tried to summarise some of the discussion around ā€œviewā€œ it happened over the last few months. It's a lot of information, with one goal: please share your thoughts.</p> <p>It doens't have to be ā€œviewsā€, if could be ā€œpurple zebrasā€, as <a href="https://github.com/w3c/wcag3/discussions/286#discussioncomment-12351879">Alastair suggested</a>, but modernising what WCAG's unit(s) of conformance are will be more helpful to users than avoiding it.</p> <p>I'd love more feedback (by email or in <a href="https://github.com/w3c/wcag3/discussions/286">the GitHub discussion</a>) on what views can be and what the potential risks are, especially from accessibility practitioners (including evaluators), folks from monitoring agencies and designers and developers who want to meet WCAG.</p> <hr/> <p>Originally posted as <a href="https://hidde.blog/views/">Views on views</a> on <a href="https://hidde.blog">Hidde's blog</a>.</p> <p><a href="mailto:hidde@hiddedevries.nl?subject=Reply%20to:%20Views on views">Reply via email</a></p> View Transitions 3d Perspective - Adam Argyle https://nerdy.dev/view-transitions-3d-perspective?utm_source=rss 2025-02-27T23:06:40.703Z <p>To give <a href="https://developer.chrome.com/docs/web-platform/view-transitions">View Transitions</a> a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/perspective">perspective</a>, either:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">::view-transition-image-pair(--foo) {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> perspective</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 500</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"><span style="color:var(--shiki-token-comment)">/* or */</span></span> <span class="line"><span style="color:var(--shiki-foreground)">::view-transition-new(--foo) {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> perspective</span><span style="color:var(--shiki-token-constant)">(500</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-token-constant)">)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>Hope this finds someone well.</p> CSS Kaleidoscopes - Adam Argyle https://nerdy.dev/css-kaleidoscopes?utm_source=rss 2025-02-23T00:21:40.428Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/css-kaleidoscope.png" alt="Short video switching between the two kaleidoscopes" height="1016" width="2522" /> <script type="module"> import "https://cdn.jsdelivr.net/npm/baseline-status"; </script> <p>Gradients are so fun, so complex, so powerful. I just can&#39;t get enough of messing with them.</p> <p>Todays post was me wanting to create a kaleidoscope type effect using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/linear"><code>linear()</code></a> easing with varied durations for <strong>hopefully infinite combinations</strong>.</p> <p>Here&#39;s a spiffy preview of some of the combinations:</p> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-kaleidoscopes.mp4 width="1514" height="1514" alt="" preload="none" poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-kaleidoscopes.jpg" controls loop muted playsinline allowFullScreen /></p> <p>The demo effects are thanks to <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/linear"><code>linear()</code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property"><code>@property</code></a>.</p> <p><baseline-status featureId="registered-custom-properties"></baseline-status> <baseline-status featureId="linear-easing"></baseline-status></p> <p>Each of the demo&#39;s has a ton of fun levers to pull in the CSS. Def try changing variables, durations, easings, and more.</p> <p><strong>It&#39;s super fun.</strong></p> <h2> Linear gradient kaleidoscope <a name="linear-gradient-kaleidoscope" href="#linear-gradient-kaleidoscope">#</a> </h2> <p> <iframe class="codepen-embed" scrolling="no" title="null" src="https://codepen.io/argyleink/embed/preview/ZYEQZaz?default-tab=result&editable=true&theme-id=43079" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true" > See the Pen <a href="https://codepen.io/argyleink/embed/preview/ZYEQZaz"> Hot text-emphasis</a> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> </p> <h2> Radial gradient kaleidoscope <a name="radial-gradient-kaleidoscope" href="#radial-gradient-kaleidoscope">#</a> </h2> <p> <iframe class="codepen-embed" scrolling="no" title="null" src="https://codepen.io/argyleink/embed/preview/MYWKdbe?default-tab=result&editable=true&theme-id=43079" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true" > See the Pen <a href="https://codepen.io/argyleink/embed/preview/MYWKdbe"> Hot text-emphasis</a> by Adam Argyle (<a href="https://codepen.io/argyleink">@argyleink</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> </p> <h2> Conic gradient kaleidoscope <a name="conic-gradient-kaleidoscope" href="#conic-gradient-kaleidoscope">#</a> </h2> <p>[YOUR FORK HERE]</p> <p>Send me your Codepen fork and I&#39;ll add it here!</p> An Emoji Naming Convention - Adam Argyle https://nerdy.dev/css-emoji-convention?utm_source=rss 2025-02-20T18:16:39.738Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/emoji-convention.png" alt="A screenshot of some of the code snippets found in this blog post." height="1078" width="1798" /> <p>There are many new places in CSS for naming. You can now quickly find yourself using the same name, which is unique, but it&#39;s unique in a contextā€¦ and that&#39;s really hard to remember.</p> <p>Here&#39;s a simple example:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> foo {}</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --foo {}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.element</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --foo</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> bar</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> foo linear both</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-timeline-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --foo</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> foo</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> anchor-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --foo</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> container-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> foo</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>Notice too how there&#39;s dashed names and undashedā€¦ some CSS features require the dashed names while others do not. Yay.</p> <p>Good news is that CSS can keep track of it all. Bad news, I&#39;m strugglin.</p> <h2> Not an answer, but an answer? <a name="not-an-answer,-but-an-answer?" href="#not-an-answer,-but-an-answer?">#</a> </h2> <p>I&#39;m not actually trying to tackle this problem with this suggestion, butā€¦ maybe I am? LOL</p> <p>We could, <strong>use emoji to help delineate the intended use</strong>. </p> <p>Here, keyframes, typed custom properties and font-palette-values are uniquely identified with <strong>an emoji prefix</strong>.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> šŸ”‘-example {}</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --ļ¹«-example {}</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@font-palette-values</span><span style="color:var(--shiki-foreground)"> --šŸ””-example {}</span></span></code></pre> </code></pre> <p>This then makes it clear when you got to use them:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.element</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --ļ¹«-example</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> bar</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> šŸ”‘-example linear both</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>So building on this, I went a little furtherā€¦ and have assigned emoji&#39;s for anything you can custom name:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> šŸ”‘-example {}</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --ļ¹«-example {}</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@font-palette-values</span><span style="color:var(--shiki-foreground)"> --šŸ””-example {}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">:root</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --ļ¹«-example-typed</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> baz</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --šŸ”’-example-private</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> foo</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --regular-var</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> bar</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)">::view-transition-group(</span><span style="color:var(--shiki-token-function)">.šŸ˜ļø-example</span><span style="color:var(--shiki-foreground)">) {}</span></span> <span class="line"><span style="color:var(--shiki-foreground)">::view-transition-new(šŸžļø-example) {}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@layer</span><span style="color:var(--shiki-foreground)"> šŸ„ž-example {</span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ¤“-example-class</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> container-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> šŸ“¦-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> šŸžļø-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-class</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> šŸ˜ļø-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-timeline-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --šŸ“½ļø-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> anchor-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --āš“-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> font-palette</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --šŸ””-example</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> šŸ”‘-example 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> > </span><span style="color:var(--shiki-token-constant)">p</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">container</span><span style="color:var(--shiki-foreground)"> šŸ“¦</span><span style="color:var(--shiki-token-constant)">-example</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">inline-size</span><span style="color:var(--shiki-foreground)"> &#x3C; 20</span><span style="color:var(--shiki-token-constant)">ch</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> font-size</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--ļ¹«-example)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@layer</span><span style="color:var(--shiki-foreground)"> šŸ„ž-example-utilities {</span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ”§-utility-class</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-foreground)"> </span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ§¹-reset-class</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-foreground)"> </span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ”-debug-class</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> ā€¦</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> </span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ—£ļø-sr-only</span><span style="color:var(--shiki-foreground)"> {}</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> </span></span> <span class="line"><span style="color:var(--shiki-token-function)"> .šŸ±-grid</span><span style="color:var(--shiki-foreground)"> {}</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><strong>Does this work?!</strong></p> <p>Yep, perfectly valid CSS. <a href="https://codepen.io/argyleink/pen/vEYLwpv">Try it on Codepen</a>.</p> <p><strong>Did I miss an opportunity to pick an emoji prefix?</strong></p> <p>Probably! Comment on Bluesky or Mastodon and I&#39;ll add them!</p> <p>As a final mention here, I think it&#39;s a good idea to <code>--</code> all your custom names, just to make it really clear they aren&#39;t internal CSS keywords. </p> Anchoreum - Adam Argyle https://nerdy.dev/anchoreum?utm_source=rss 2025-02-19T20:41:57.587Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/anchoreum.png" alt="Screenshot of the app editor" height="1828" width="3172" /> <p>Need a rad way to learn CSS <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/anchor">anchor</a>?</p> <p>Look no further than <a href="https://anchoreum.com">anchoreum.com</a>!</p> 39 Today - Adam Argyle https://nerdy.dev/39-today?utm_source=rss 2025-02-19T19:06:43.139Z <p>39 today, wheeeeā€¦</p> trot - Robin Rendle https://robinrendle.com/notes/trot/ 2025-02-15T18:06:40.131Z <p>Iā€™ve been tinkering with an iOS app for the last month or so. Itā€™s exciting stuff! Working in Swift and moving away from the web has made my work feel all new and fresh, with so many things to learn. The project codename is Trot (for now!) and hereā€™s some notes about what Iā€™ve learned so far.</p> <hr /> <p>Leaving VSCode and the browser behind by moving into XCode has made me immediately miss DevTools. Itā€™s so much harder to prototype when you canā€™t zoom into the UI and crack it open like you can in the browser.</p> <p>XCode does have an equivalent of that but itā€™s not front and center or half as easily accessible as DevTools. This realization of losing my pal DevTools honestly made me sad as I tinkered with this app for the first time, knowing that no one will be able to explore this app in the same way they might with my website. DevTools is the great equalizer.</p> <hr /> <p>Swift and XCode are incredibly well built and fit so nicely together. Owning the language and the tools means that this ecosystem can do some amazing things that web frameworks and text editors could only dream of. SwiftUI sort of forces you to build everything in small chunks instead of whole screens and Iā€™ve found that I can lean into that and let it guide how I structure the code.</p> <p>This ecosystem of tools and how they overlapā€¦wellā€¦the web really has nothing like this. For good and bad!</p> <hr /> <p>Next steps are difficult to find though. You canā€™t move without watching a million videos or reading the docs: UIKit, MapKit, SwiftUI, Motion, animations. This is true if I create a .html file in VSCode but thereā€™s also fifteen years of tinkering I can rely on. I know what HTML and CSS is capable of so I donā€™t need to ask questions every thirty seconds about how to style something or move its position.</p> <p>This is a good reminder that the tools look simple to us web developers but they are very much <em>not</em> easy for newcomers to the field.</p> <p>Anyway, thereā€™s nothing in XCode that opens a door into these worlds or shows you whatā€™s next. Nothing hints at whatā€™s possible. You have to leave the editor to go explore and it totally kills the flow of exploration to ask a bot or a search engine how to add padding or color every three seconds. Itā€™s like someone is slamming a door in your face over and over again.</p> <p>I wanna be like: hereā€™s some tabs on the screen! Okay, now show me all the ways I can customize those! Or show me how to animate them! Loop through the items and filter them or list out all the appearance options and frameworks I can tinker with! Show me the doors!</p> <p>Autocomplete today is insufficient and I think the main reason why folks rely on Copilot or asking a bot is because these tools are really extraordinarily bad at showing you whatā€™s possible and then how to do it.</p> <hr /> <p>I LOVE not having to import things in Swift. Sure, you still import frameworks at the top of a view but itā€™s not like a typical React codebase where every file begins with 50 lines of junk importing stuff.</p> <p>This means I can reorganize files and folders and it doesnā€™t matter because nothing will break. I have never liked React/Typescript ā€” every part of that ecosystem feels clunky and obtuse ā€” but this feature of Swift and XCode alone makes web frameworks feel like an ancient system designed by complete sadists.</p> <p>Building in Swift and Xcode almost (almost!) has the same feeling of opening up a text editor and writing pure HTML. It feels light and springy in a way that no web framework ever has.</p> <hr /> <p>Hot take: I donā€™t like the merge of semantic meaning and styles within Swift though. Itā€™s like designing a whole UI with jQuery where youā€™re like ā€œThing.color(red).padding(10)ā€ and it means itā€™s harder to parse the logic from the presentation. I get why itā€™s like that, that Swift breaks everything down into a hierarchy, a great big tree under the hood, but it means your code is all jumbled together.</p> <p>I assume Apple-folk would say there is no difference between style and presentation and logic but manā€¦the web has taught me a different way of seeing the world and this editor, these tools, conflict with every fiber of my being. But perhaps thatā€™s a good thing.</p> <hr /> <p>I realize how lazy at programming I am. I canā€™t take one type of data and convert it to another. I hate dealing with types and keeping things in my head across files. It all feels so fragile and precarious. I hate dealing with state because all of a sudden you introduce punctuation that makes my head spin. I donā€™t think logically, I hop skip and jump around and my body refuses to think like a computer or a program.</p> <p>Thankfully each stumbling block can now be queried and the hive mind will respond with an accurate enough solution.</p> <p>But at the lowest levels I have a very vague idea of whatā€™s going on under the hood. For a prototype thatā€™s ok, but if this thing gets more serious Iā€™d want an engineer to start over. Scrap it all and go again ā€“ and that makes me feel better as I tinker. None of this matters! Wahoo!</p> <hr /> <p>Another thing I miss? HTML defaults. When you use an <code>&lt;input&gt;</code> or a <code>&lt;button&gt;</code> in HTML you mostly donā€™t have to <em>undo</em> things. Except for forms which have always driven me crazy that they donā€™t inherit the font family or what not from <code>body</code>. But tiny rant aside, the defaults in HTML are very smart and you can build on those defaults by changing the background, the font, etc.</p> <p>In SwiftUI you can use common components like <code>List</code> but to tweak their behavior or appearance you have to break them down into a million pieces, hacking over them to make a slight change. Want your tabs to look a bit different and have a white background? Good luck buddy, that change is gonna take you an hour.</p> <p>This discovery sucks. Itā€™s like having a design system where you pull in a component and to change the border radius of it you have to build a whole new component out of the skeleton of the button right in front of you. I can see you! Just be round!</p> <p>The design of HTML and CSS has spoiled me rotten, man.</p> <hr /> <p>If you get a reference to an image wrong in Swift-land then your whole app implodes and nothing will run. Working on this project is great but ten minutes into it and I already miss <a href="https://resilientwebdesign.com/">the resilience of the web</a>. I miss how you have to really fuck things up to make a browser yell at you or implode.</p> <p>At least on the kinds of web projects I work on, I rarely break the browser or my text editor Zed, but with pretty high frequency XCode will freak out and crash. That kills the flow too, where I might just be changing a variable and ā€” bang! ā€” the preview is dead and I get a popup alert blocking my work.</p> <p>It makes app development feel less stable somehow and I wish there was a way XCode could figure out the code Iā€™m writing is gonna crash before it tried to build it ā€” as I was typing.</p> <hr /> <p>Making animations in SwiftUI feels so liberating though! It is light years ahead of the web because thereā€™s tons of really smart defaults that Apple provided in what I assume was the original operating system. Things bounce and glide and itā€™s real easy to get things to move in the way you want them to.</p> <p>Honestly Iā€™ve never liked animating things on the web. Itā€™s just such as royal pain and itā€™s very slow. In my day job as a product designer on a big web app I tend avoid animations because they often get in the way or slow things down but most of the time itā€™s laziness: animations are really hard to pull off on the web and most of the time theyā€™re not worth the effort.</p> <p>But here in SwiftUI? Itā€™s so easy to make beautiful animations that you canā€™t help but sprinkle them everywhere. This might also be thanks to touch input. When you touch something with your finger you expect it to respond like a physical thing in the world. And without animations that touch would feel broken as all hell.</p> <p>Honestly this is changing how Iā€™m thinking about this interface: How can movement explain where someone is in the UI? How can that help teach them what might happen next? How can this animation fade into the next one, click-clacking into each other like ripples in a lake?</p> <p>Itā€™s all super exciting if Iā€™m honest. I havenā€™t felt this excited about design in a good long while. Learning new tools, new techniques, new ways to do old things.</p> <hr /> <p>With this project I can truly fly: I am beholden to no-one. No one can tell me my designs are impossible. No one can tell me to do more research. I can just do everything instinctively, intuitively, feel things out. Who gives a shit about efficiency? Who cares about process? I just care about making something cool and learning something new.</p> <p>Itā€™s all so quiet as I build this app and bop my head to the music. There are no quarterly goals, there are no cursed Google docs, there are no lists within lists or anxious meetings.</p> <hr /> <p>I like the playful nature of this project, too. In the year 2025, design at a tech company is so absurdly serious. All the joy of making things is wrenched out of the work by bureaucracy or unnecessary meetings or folks higher up the chain who are anxious about every detail. Have you done the Discovery phase of your project yet? Have you confirmed that every pixel of this screen meets the expectations of this random Google Doc? How do we know that this design will be successful, will make this number go up?</p> <p>I dunno, man.</p> <p>But here, on this project, the research is the work. I have to build things to learn. Writing a doc is not learning. Writing a doc is not making. Adding tickets to Linear is not research. Talking to customers for six months straight without putting pen to paper is not work. It sounds dumb and arrogant of me, but here in my room as I tinker I feel renewed by all this real work. I feel close to it, cleansed by thinking about a single problem for hours at a time without distraction.</p> <p>This, right here, is the research. This is the work.</p> <p>And thereā€™s no Google doc in sight.</p> What I learned from migrating a Vue project from Vuex to Pinia - CSS In Real Life https://css-irl.info/what-i-learned-from-migrating-a-vuex-app-to-pinia/ 2025-02-13T14:01:43.942Z <p>Recently I had the experience of migrating a Vue web app to a new state management library, Pinia, which was interesting from both a technical and non-technical point of view. In this article for Piccalilli Iā€™ll share my thoughts and findings on when, why and how you might consider carrying out such a major technical migration, applicable to any large web project, and some tips and advice on migrating from Vuex to Pinia specifically.</p> <p><a class="o-hotlink" href="https://piccalil.li/blog/what-i-learned-from-migrating-a-vue-project-from-vuex-to-pinia/" target="_blank">Read the article</a></p> Debating the Merits of LLMs - CSS In Real Life https://css-irl.info/debating-the-merits-of-llms/ 2025-02-13T14:01:43.910Z <p>A recently published post by the science fiction writer Robin Sloan (<em><a href="https://www.robinsloan.com/lab/is-it-okay/">Is It Okay?</a></em>, published 11th February 2024) ignited some examination and debate among my little corner of the web. The post asks the question of whether itā€™s ethical, from an individual moral standpoint to use an LLM (<a href="https://en.wikipedia.org/wiki/Large_language_model">Large Language Model</a>, such as Claude or GPT-4). Robin raises some important points about the trade-offs that come with LLMs, depending on their application.</p> <blockquote> <p>If their primary application is to produce writing and other media that crowds out human composition, human production: no, itā€™s not okay.</p> </blockquote> <p>He also offers an alternative view, where it could be supposed that LLMs will pave the way for ā€œsuper scienceā€, a common claim of AI advocates.</p> <blockquote> <p>If super science is a possibilityā€Šā€”ā€Šif, say, Claude 13 can help deliver cures to a host of diseasesā€Šā€”ā€Šthen, you know what? Yes, it is okay, all of it. Iā€™m not sure what kind of person could insist that the maintenance of a media status quo trumps the eradication of, say, most cancers. Couldnā€™t be me. Fine, wreck the arts as we know them. Weā€™ll invent new ones.</p> </blockquote> <p>Hereā€™s where I disagree with Robinā€™s reasoning: AI <em>isnā€™t</em> LLMs. Or not <em>just</em> LLMs. Itā€™s plausible that AI (or more accurately, Machine Learning) could be a useful scientific tool, particularly when it comes to making sense of large datasets in a way no human could with any kind of accuracy, and many people are already deploying it for such purposes. This isnā€™t entirely without risk (Iā€™ll save that debate for another time), but in my opinion could feasibly constitute a legitimate application of AI.</p> <p>LLMs are <em>not this</em>. They synthesise <em>text</em>, which is not the same as <em>data</em>. Particularly when they are trained on the entire internet, which we all know includes a lot of incorrect, discriminatory and dangerous information. As <a href="https://www.baldurbjarnason.com/notes/2025/subtly-wrong-is-more-dangerous">Baldur Bjarnason points out</a>:</p> <blockquote> <p>There is no path from language modelling to super-science.</p> </blockquote> <p>I donā€™t believe LLMs are entirely without utility. The company I work for designs and trains AI models for use in industrial processes <em>and</em> LLMs. But they are different things. In one application we (and by ā€œweā€, I mean my far cleverer colleagues) deploy models for analysing performance data from wind turbines to produce insights related to power output, deterioration and part failure, in order to enable operators to plan maintenance and optimise power generation. Here AI has the <em>potential</em> to help drive down costs and maximise clean energy production. Itā€™s still early days, and it remains to be seen whether this kind of technology will be widely deployed and beneficial at a large scale, but this, to my mind, edges marginally towards the scientific potential that Robin refers to (while being a long way from, say, curing cancer). Itā€™s <em>not</em> an LLM.</p> <p>On the other hand, we <em>do</em> train LLMs for other applications, such as gleaning relevant information from thousands of disparate documents, which would be impossible to trawl through manually, and present findings in a user-friendly way. This is not a general-purpose LLM designed to regurgitate information from the entire internet, but is built from a set of highly specific training data that is relevant to the industry in which it is applied.</p> <p>Both of these applications are interesting and potentially useful. But they are not the same. An LLM as described above, while useful, <strong>shouldnā€™t invent new information</strong>. It processes the text that already exists, not the science behind it, and if it appears to offer up something new then that should be met with the utmost scrutiny. And it remains to be seen whether they (and others like them) will be worth the extraordinary amount of energy and resources that AI demands.</p> <p>By using Chat-GPT to write your essay, code or email, you are not contributing to ā€œsuper scienceā€. LLMs cannot do that. Maybe youā€™ll conclude that using an LLM is still worth it to make you more productive in writing code, <a href="https://scienceblog.com/ai-tools-make-workers-less-critical-more-confident-microsoft-study-finds/">or whatever</a>. (And yes, I have Thoughts on this.) But once we discount ā€œsuper scienceā€ from the equation, it seems to me there arenā€™t a whole lot of positives left.</p> Scroll Driven Animations Notebook - Adam Argyle https://nerdy.dev/scroll-driven-animations-notebook?utm_source=rss 2025-02-06T06:11:40.005Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/sda-notebook.png" alt="A grid of zebra striped scrollers, each with various animations in mid effect." height="1680" width="2520" /> <p>For chill time I&#39;m adding to this <a href="/notebook/scroll-driven-animations.html">scroll driven animations notebook</a>.</p> <p>Some pretty wild stuff in there already, gettin wierder each bedtime routine.</p> New 404 Page - Adam Argyle https://nerdy.dev/new-404-page?utm_source=rss 2025-02-03T00:38:11.686Z <p>New <a href="/the-404-page">404 page</a>:</p> <ul> <li>glitchtastic</li> <li><a href="https://chromewebstore.google.com/detail/visbug/cdockenadnadldjbbgcallicgledbeoc">VisBug</a> loaded so you can <br><strong>edit</strong> or <strong>destroy</strong> the page</li> </ul> New Footer Went Big - Adam Argyle https://nerdy.dev/new-footer-went-big?utm_source=rss 2025-02-03T00:38:11.655Z <p>New <code>&lt;footer&gt;</code>,<br> Went big.</p> Notes on blogging - Robin Rendle https://robinrendle.com/notes/notes-on-blogging/ 2025-02-01T21:22:11.035Z <p>The ever-so-great-and-interesting <a href="https://kayserifserif.place/posts/2025/ive-been-tagged">Katherine</a> answered some questions about blogging and tagged me to do the same.</p> <h2 id="why-did-you-start-blogging-in-the-first-place" tabindex="-1">Why did you start blogging in the first place? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#why-did-you-start-blogging-in-the-first-place" aria-hidden="true">#</a></h2> <p>In my late teens the web wouldnā€™t let me go. My favorite hobby was staying up until 2am reading smart things from smart people so very far away because it felt like an act of rebellion. Hundreds, thousands of people, writing about the web and its potential, about where it should all go next. They were so open and honest! They wrote about love and code, their struggles with their family. They wrote about trying to be better people but failing over and over again.</p> <p>I was obsessed with <a href="https://robinrendle.com/notes/a-crowd-of-sorrows/">ftrain</a>, and devoured everything that <a href="https://robinrendle.com/notes/chloe/">Chloe</a> wrote (I still miss her, even though we never met). I read travelogues and private diaries and book reviews, or breakdowns of conferences in distant cities, and I would stay up all night swooning over design blogs like <a href="https://midcenturymodernist.com/">The Mid-Century Modernist</a>.</p> <p>Blogging was a Great Struggle Machine, a tool to bring brilliant people into my life, and it was through that ritual where we could all be more honest with each other than we might be in person. It was exciting!</p> <p>My first blog was a Wordpress theme I bought from <a href="https://ia.net/">IA</a> all about web design. It was really embarrassing and thatā€™s me being kind to myself. I had Big Opinions about design that were just annoying regurgitations from other smart folks. But over the years I kept tinkering, eventually making my own website, and thatā€™s where Iā€™ve posted things for the best part of 10 years now.</p> <p>Through blogging I hope to pay something back to the community that once gave so much to me.</p> <h2 id="what-platform-are-you-using-to-manage-your-blog-and-why-did-you-choose-it-have-you-blogged-on-other-platforms-before" tabindex="-1">What platform are you using to manage your blog and why did you choose it? Have you blogged on other platforms before? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#what-platform-are-you-using-to-manage-your-blog-and-why-did-you-choose-it-have-you-blogged-on-other-platforms-before" aria-hidden="true">#</a></h2> <p>Through blogging I learned about web design and every designer back then argued to own the means of production: own your website! Donā€™t use a third party blogging platform because it could all be taken down, deleted, or ransacked.</p> <p>After the Bad WordPress Blog Who Shall Not Be Named, I built a blog in Jekyll before landing on <a href="https://www.11ty.dev/">Eleventy</a> which is what Iā€™ve used for years now. I tried other systems like <a href="http://blot.im/">Blot.im</a> but all of them fail where Eleventy thrives. Itā€™s easy to use, easy to customize, and I can tear it all out if I want to try something else. Also, Zach rules.</p> <h2 id="how-do-you-write-your-posts-for-example-in-a-local-editing-tool-or-in-a-paneldashboard-that's-part-of-your-blog" tabindex="-1">How do you write your posts? For example, in a local editing tool, or in a panel/dashboard thatā€™s part of your blog? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#how-do-you-write-your-posts-for-example-in-a-local-editing-tool-or-in-a-paneldashboard-that's-part-of-your-blog" aria-hidden="true">#</a></h2> <p>Mostly iA Writer on my phone. Iā€™ve tried other editing tools to help organize things but I like a single big folder full of endless notes. Thatā€™s tied up to Dropbox so I can turn those scribbles into something more thoughtful eventually.</p> <p>Once Iā€™ve got it in a good shape I run <a href="https://robinrendle.com/notes/taking-shortcuts/">a macOS shortcut</a> that takes the contents of my clipboard, opens up VS Code, creates a file, and pastes it in there. But gosh if Shortcuts.app isnā€™t the buggiest, weirdest way to do this and continually breaks which prevents me from wanting to write.</p> <p>Iā€™ll fix it eventually, I promise.</p> <h2 id="when-do-you-feel-most-inspired-to-write" tabindex="-1">When do you feel most inspired to write? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#when-do-you-feel-most-inspired-to-write" aria-hidden="true">#</a></h2> <p>Early mornings. Coffee in hand. Sitting in a roomful of strangers with the rain outside. That is the perfect writing environment.</p> <p>But often I feel inspired by other bloggers and web-folk. Just the other day I fell down a wondrous e-hole thanks to <a href="https://maya.land/">maya.land</a> and the bountiful links posted everywhere on her brilliantly strange website. It made me want to make weird websites again.</p> <h2 id="do-you-publish-immediately-after-writing-or-do-you-let-it-simmer-a-bit-as-a-draft" tabindex="-1">Do you publish immediately after writing, or do you let it simmer a bit as a draft? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#do-you-publish-immediately-after-writing-or-do-you-let-it-simmer-a-bit-as-a-draft" aria-hidden="true">#</a></h2> <p>Immediately. If I donā€™t get a post out in the second it feels important then I know itā€™ll languish in drafts forever.</p> <p>Iā€™ve often looked back on a note though and realized I was too fast though, too quick to post where I was careless with the idea and couldā€™ve expanded on it so much more. Everything I post in <a href="https://robinrendle.com/stories/">/stories</a> typically takes months to write and think about though.</p> <h2 id="what's-your-favourite-post-on-your-blog" tabindex="-1">Whatā€™s your favourite post on your blog? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#what's-your-favourite-post-on-your-blog" aria-hidden="true">#</a></h2> <p>Oh, man. Reading old posts now is enjoyable because I donā€™t remember writing them, it feels like Iā€™m intruding on someone elseā€™s blog at this point. But thereā€™s certainly a style of post that Iā€™m now tired of and try to avoid: any writing about my day job or about code and design or how to build products is so...lame. Thereā€™s an anger in those posts that feels wasteful and I often regret writing them.</p> <p>The best posts are the ones where I overshare about my life, where I let the reader in on a secret I shouldnā€™t be telling them. So I like this post about <a href="https://robinrendle.com/notes/moving-is-not-failure/">moving out of my apartment</a>, I like this one about <a href="https://robinrendle.com/notes/the-comforts-of-the-siren/">the sirens back home</a>, I like <a href="https://robinrendle.com/notes/blunder/">Blunder</a> or <a href="https://robinrendle.com/notes/the-computer-is-a-feeling/">The computer is a feeling</a>, or <a href="https://robinrendle.com/notes/church-going/">Church Going</a> (YIKES), or <a href="https://robinrendle.com/notes/notes-on-blogging/notes/like-clockwork/">Like Clockwork</a> and <a href="https://robinrendle.com/notes/potential-and-loss/">Potential and Loss</a> and <a href="https://robinrendle.com/notes/a-thousand-ships/">A Thousand Ships</a>. Oh, oh, <a href="https://robinrendle.com/notes/i-am-a-poem-i-am-not-software/">I am a poem I am not software</a>, too.</p> <p>In terms of tone, pacing, visuals, and everything else though ā€” <a href="https://robinrendle.com/stories/newsletters/">Newsletters</a> has been the high point probably.</p> <h2 id="any-future-plans-for-your-blog-maybe-a-redesign-a-move-to-another-platform-or-adding-a-new-feature" tabindex="-1">Any future plans for your blog? Maybe a redesign, a move to another platform, or adding a new feature? <a class="direct-link" href="https://robinrendle.com/notes/notes-on-blogging/#any-future-plans-for-your-blog-maybe-a-redesign-a-move-to-another-platform-or-adding-a-new-feature" aria-hidden="true">#</a></h2> <p>Iā€™m endlessly redesigning this guy because it calms me but I think the heat death of the universe will arrive before I bail on Eleventy.</p> <p>I do feel like my website is too visually cramped at the moment. Itā€™s like reading on a little card or note and I always want to take up all the screen real estate that I can and really show off typographically. So I expect next month I might blow it all up and try again.</p> Bad At CSS With Lane Wagner - Adam Argyle https://nerdy.dev/bad-at-css-with-lane-wagner?utm_source=rss 2025-01-21T16:45:53.445Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/bad-at-css-logo.png" alt="Bad at CSS logo" height="426" width="1122" /> <p><span class="Tag">Ep #14</span><br>šŸŽ™ļø <strong>Bad At CSS Podcast</strong></p> <p>Backend dev <a href="https://x.com/wagslane">Lane Wagner</a>, of <a href="https://www.boot.dev/">boot.dev</a> and host of <a href="https://www.backendbanter.fm/">Backend Banter FM</a>, guests this week sharing <strong>what do backend dev think of CSS.</strong></p> <p>ā¤· <a href="https://badatcss.com">badatcss.com</a> Ā· <a href="https://www.youtube.com/watch?v=UgBUlaMaM28">youtube</a> Ā· <a href="https://open.spotify.com/episode/6KY19rcsp783fXDrdsiCxK">spotify</a></p> Change and risk - Robin Rendle https://robinrendle.com/notes/change-and-risk/ 2025-01-20T19:36:17.008Z <p>Kelly Sutton on <a href="https://kellysutton.com/2025/01/18/moving-on-from-react-a-year-later.html">why his team views big front-end frameworks as a liability</a>:</p> <blockquote> <p>Maybe itā€™s the changing interest rates or political winds, but I think the ā€œfat clientā€ era JS-heavy frontends is on its way out. The hype around edge applications is misplaced and unnecessary for building many different flavors of successful businesses.</p> </blockquote> <p>My translation here: whatā€™s good for business often isnā€™t whatā€™s cool or tracking at the top of the orange website or what has most likes on GitHub. A product can be <em>extremely</em> useful and interesting and still be on the most boring tech stack imaginable.</p> <p>But also, this bit:</p> <blockquote> <p>Change comes with risk, and changing untested code has a higher regression risk. [...] Moving more slowly is fine in many cases. But when compared to a world where that change isnā€™t risky (server-rendered ERB), we are suddenly paying a very pricey tax for making changes in JavaScript.</p> </blockquote> <p>At most places Iā€™ve worked at, any amount of change feels <em>extremely</em> risky and that sentiment at one point or another moves out of the codebase and into every meeting and discussion. So I think that as a small dev team, where every moment is precious, perhaps the most important thing is reliability in a codebase.</p> <p>And big, overly complicated JS-heavy frontends are anything but reliable.</p> 6 CSS Snippets Every Front-End Developer Should Know In 2025 - Adam Argyle https://nerdy.dev/6-css-snippets-every-front-end-developer-should-know-in-2025?utm_source=rss 2025-01-19T21:28:36.998Z <img style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/image/upload/argyleink/6-css-snippets-2025-thumb.png" alt="Text emphasized alt text example" height="525" width="1019" /> <script type="module"> import "https://cdn.jsdelivr.net/npm/baseline-status"; </script> <p>2025; I think every front-end developer should know how to enable <a href="#view-transitions-for-web-pages">page transitions</a>, transition a <a href="#transition-a-dialog"><code>&lt;dialog&gt;</code></a>, <a href="#transition-a-popover">popover</a>, and <a href="#transition-animation-for-%3Cdetails%3E"><code>&lt;details&gt;</code></a>, animate light n&#39; dark <a href="#animated-adaptive-gradient-text">gradient text</a>, <a href="#typed-custom-properties">type safe their CSS</a> system, and <a href="#springy-easing-with-%3Ccode%3Elinear()%3C/code%3E">add springy easing</a> to animation.</p> <p><strong>AI is not going to give you this CSS.</strong></p> <p>This post is a theme continuation; checkout previous years <a href="https://web.dev/articles/6-css-snippets-every-front-end-developer-should-know-in-2023">2023</a> and <a href="https://web.dev/articles/5-css-snippets-every-front-end-developer-should-know-in-2024">2024</a> where I shared snippets for those years.</p> <p>This year, the snippets are bigger, more powerful, and leverage progressive enhancement a bit more; to help us step up to the <strong>vast UI/UX requirements of 2025</strong>.</p> <h2> Springy easing with <code>linear()</code> <a name="springy-easing-with-<code>linear()</code>" href="#springy-easing-with-<code>linear()</code>">#</a> </h2> <p><baseline-status featureId="linear-easing"></baseline-status></p> <p>Sprinkle life into animations with natural looking spring and bounce easings using <code>linear()</code>.</p> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-linear.mp4 width="1916" height="1080" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-linear.jpg" controls loop muted playsinline allowFullScreen /></p> <p>Using a series of linear lines to make &quot;curves&quot;, you can create surprisingly realistic visual physics. <strong>A small amount can go a long way</strong> to adding <a href="https://codepen.io/argyleink/pen/PoxgOZz">interest and intrigue</a> to the user experience.</p> <p>In the following video, the top animation uses <code>ease-out</code> and the bottom uses <code>linear()</code>, and I think the results are quite different, the bottom being more desirable.</p> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/linear-easing-compared.mp4 width="1280" height="962" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/linear-easing-compared.jpg" controls loop muted playsinline allowFullScreen /></p> <p>Here&#39;s some typical <code>linear()</code> easing CSS šŸ˜…:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.springy</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transform 1</span><span style="color:var(--shiki-token-keyword)">s</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> linear(</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.009</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.035 2.1</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.141</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.281 6.7</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.723 12.9</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.938 16.7</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.017</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.077</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.121</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.149 24.3</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.159</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.163</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.161</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.154 29.9</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.129 32.8</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.051 39.6</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.017 43.1</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.991</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.977 51</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.974 53.8</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.975 57.1</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 0.997 69.8</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.003 76.9</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1.004 83.8</span><span style="color:var(--shiki-token-keyword)">%</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> 1</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> );</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><small>Yesā€¦ that&#39;s what your formatter will do to it, as if it&#39;s helpful in some way lol.</small></p> <p>The <code>linear()</code> code above is not very human readable, but the machines love it. No frets, there&#39;s a few generators out there:</p> <ul> <li><a href="https://linear-easing-generator.netlify.app">https://linear-easing-generator.netlify.app</a></li> <li><a href="https://easingwizard.com">https://easingwizard.com</a></li> </ul> <q class="tip"> <p><strong>Tip!</strong><br>Expect longer durations when using <code>linear()</code>. When things run long, it can be nice to make them seamlessly interruptible, making <code>linear()</code> a great fit for <a href="https://developer.mozilla.org/docs/Web/CSS/transition">transitions</a> and potentially troublesome as keyframes.</p> </q> <p>You could alternatively use premade CSS variables from a library like <a href="https://open-props.style/#easing">Open Props</a>:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@import</span><span style="color:var(--shiki-token-string-expression)"> "https://unpkg.com/open-props/easings.min.css"</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.springy</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transform 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--ease-spring-3)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>Easy CSS to read, comes with 5 strengths for common effects:</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-linear-op.mp4 width="1198" height="422" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-linear-op.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/pen/ZEXQovz">Try it</a></p> </figcaption> </figure> <q class="notebook"> <p><a href="/notebook/open-props-springs.html">Try the <strong>Open Props Springs</strong> notebook!</a></p> </q> <h3> Incrementally adopt <a name="incrementally-adopt" href="#incrementally-adopt">#</a> </h3> <p>This one is super easy to toss in today.</p> <p>Easiest way, use the cascade (if you&#39;re into that):</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-comment)">/* just repeat the shorthand with adjusted easing */</span></span> <span class="line"><span style="color:var(--shiki-token-function)">.thingy</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transform 0.3</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> ease</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transform 0.3</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> linear(ā€¦</span><span style="color:var(--shiki-foreground)">);</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)">/* or, target a specific property */</span></span> <span class="line"><span style="color:var(--shiki-token-function)">.thingy</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation-timing-function</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--ease-1)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation-timing-function</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--ease-spring-2)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><strong>If it knows, it knows.</strong></p> <p>Or, test for it first and scalpel apply the upgrade:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.thingy</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transform 0.3</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> ease</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">supports</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">transition-timing-function</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> linear(0</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> 0.1</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> 1</span><span style="color:var(--shiki-foreground)">)) {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition-timing-function</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--ease-spring-2)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <h2> Typed custom properties <a name="typed-custom-properties" href="#typed-custom-properties">#</a> </h2> <p><baseline-status featureId="registered-custom-properties"></baseline-status></p> <p>Similar to JS variables defined with <code>var</code>, CSS variables defined with <code>--</code> are global, loose, dynamic and flexible. This is great.</p> <p>Butā€¦ there are times, <strong>like when building a system</strong>, that you want to limit what goes into variables so a system can run with a reasonable amount of reliability.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-typed-property.mp4 width="1920" height="1080" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-typed-property.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/pen/qBwKmNe">Try it</a></p> </figcaption> </figure> <p>In the above video, a variable is set to an invalid color. At first, this breaks the system. But, <a href="https://web.dev/blog/at-property-baseline"><code>@property</code></a> is added, the system remained functioning with the latest known valid color value.</p> <p>Create a typed <code>&lt;color&gt;</code> CSS variable like this:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-1 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: rebeccapurple;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>In addition to type safety, <code>@property</code> defined variables can also be animated because the browser can infer the steps needed to interpolate the value change based on the assigned type.</p> <p>Before <code>@property</code>, the browser couldnā€™t derive a type and discover interpolation steps, it was too complicated. Now, you give the browser a hint, and it&#39;s simple.</p> <p>In 2025, y&#39;all front-end devs should be getting familiar with defining variables with <code>@property</code> because it:</p> <ol> <li>Formalizes CSS system interfaces</li> <li>Protects CSS systems</li> <li>Enables new animation powers</li> <li>Can <a href="https://web.dev/blog/at-property-performance">perform better</a> when using <code>inherits: false</code></li> </ol> <details class="resources"> <summary>Resources</summary> <ul> <li><a href="https://codepen.io/argyleink/pen/vYPdBOO">https://codepen.io/argyleink/pen/vYPdBOO</a></li> <li><a href="https://www.youtube.com/watch?v=tSfSY3Ni3X0&amp;t=3409s">https://www.youtube.com/watch?v=tSfSY3Ni3X0&amp;t=3409s</a></li> <li><a href="https://nerdy.dev/cant-break-this-design-system">https://nerdy.dev/cant-break-this-design-system</a></li> <li><a href="https://nerdy.dev/type-guarded-css-systems-with-@property">https://nerdy.dev/type-guarded-css-systems-with-@property</a></li> <li><a href="https://www.epicweb.dev/talks/lightning-in-a-bottle-with-css-custom-properties">https://www.epicweb.dev/talks/lightning-in-a-bottle-with-css-custom-properties</a></li> <li><a href="https://codepen.io/argyleink/pen/MWZMxNN">https://codepen.io/argyleink/pen/MWZMxNN</a></li> </ul> </details> <h2> View transitions for page navigation <a name="view-transitions-for-page-navigation" href="#view-transitions-for-page-navigation">#</a> </h2> <p><baseline-status featureId="cross-document-view-transitions"></baseline-status></p> <p>Y&#39;all should know how to crossfade pages when links are clicked with this tiny view transitions snippet:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@view-transition</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> navigation: auto;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt.mp4 width="1920" height="1092" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/project/full/DezgjV">Try it</a></p> </figcaption> </figure> <p><strong>This is the easiest snippet to add to your site, with no downsides.</strong></p> <p>It signals your website would like to use page transitions when links are clicked, and the default transition is a crossfade.</p> <p>If the browser doesn&#39;t support it, it continues as it always has; but if it does support it then you dab the page with some special sauce.</p> <p>There&#39;s plenty more customization you can do, like <a href="https://codepen.io/argyleink/project/editor/ZBnpqB">full page animations</a>. But the gist of this section is just to share that easy snippet and the way the feature can be progressively enhanced.</p> <h3> Incrementally adopt <a name="incrementally-adopt" href="#incrementally-adopt">#</a> </h3> <p>There&#39;s many more opportunities to add additional animations with the page transition.</p> <p>A great place to start enhancing this page transition experience is to identify elements commonly found across pages, and give them a <code>name</code>:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.nav</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --nav</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.sidenav</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --sidenav</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><strong>This includes elements in the page transition.</strong></p> <p>They can even be different elements.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-string-expression)">a</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-string-expression)"> h1</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> view-transition-name</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> --morphy</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>By giving an <code>&lt;a&gt;</code> and an <code>&lt;h1&gt;</code> the same <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name"><code>view-transition-name</code></a> on two different pages, the browser will move and resize the page 1 element to the location and size of the page 2 element, making it look like a morph. You can of course morph between the same elements also.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt-morphy.mp4 width="1920" height="1092" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt-morphy.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/project/full/AbvgrM">Try it</a></p> </figcaption> </figure> <p>There is so much more. Continue giving elements names and studying the <a href="https://view-transitions.chrome.dev/">rad examples by Bramus</a>, and you can create experiences with motion like this:</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt-rad.mp4 width="1920" height="1092" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-vt-rad.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://view-transitions.chrome.dev/off-the-beaten-path/mpa/">Try it</a></p> </figcaption> </figure> <p>I love the <a href="https://developer.chrome.com/docs/devtools/css/animations">DevTools for Animations</a>, scrubbing that full page view transition is very satisfying, and excellent for really inspecting and improving the little details.</p> <details class="resources"> <summary>Resources</summary> <ul> <li><a href="https://www.youtube.com/watch?v=eY6C_-aDdTo">Multi-page application View Transitions are here</a></li> <li><a href="https://www.youtube.com/watch?v=oXSFwix7eR8">Modern CSS for Sites Workshop</a></li> <li><a href="https://www.youtube.com/watch?v=xBEvOh9jlis">The CSS Podcast - #89 on View Transitions</a></li> <li><a href="https://codepen.io/argyleink/project/editor/ZBnpqB">Full page transitions starter</a></li> <li><a href="https://github.com/bramus/ie-page-transitions">Bramus with a kit for IE Page-like Transitions</a></li> </ul> </details> <h2> Transition animation for <code>&lt;dialog&gt;</code> and <code>[popover]</code> <a name="transition-animation-for-<code>&lt;dialog&gt;</code>-and-<code>[popover]</code>" href="#transition-animation-for-<code>&lt;dialog&gt;</code>-and-<code>[popover]</code>">#</a> </h2> <p>In 2025, knowing your way around a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><code>&lt;dialog&gt;</code></a> and a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover"><code>[popover]</code></a> are table stakes. Otherwise, everyone else will be on top of you, and your wack <code>z-index</code> attempts will be defeated with a puny value of <code>1</code>.</p> <p>These are common UI elements, with no JavaScript to download, and accessibility built in. <strong>Use em</strong> and <a href="https://hidde.blog/dialog-modal-popover-differences/">know the differences</a>.</p> <p><baseline-status featureId="dialog"></baseline-status> <baseline-status featureId="popover"></baseline-status></p> <p>These two elements are projected into a layer above all the other UI called the <a href="https://developer.chrome.com/blog/what-is-the-top-layer">top layer</a>. The browser projects the elements from anywhere in the document, to the top when shown.</p> <p><strong>To transition this</strong>, thereā€™s a few new CSS properties, for the full <em>interruptible</em> CSS transition user experience ā€” <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior"><code>transition-behavior</code></a>, the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style"><code>@starting-style</code></a> rule, and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overlay"><code>overlay</code></a>.</p> <p><baseline-status featureId="transition-behavior"></baseline-status> <baseline-status featureId="starting-style"></baseline-status> <baseline-status featureId="overlay"></baseline-status></p> <p>Combining these can feel like an incantation, but that makes it great copy and paste. So here! Use the following snippet to enable cross fade transitions for both <code>&lt;dialog&gt;</code> and <code>popover</code>: <a href="https://codepen.io/argyleink/pen/zYbQBOm">Try it</a></p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-comment)">/* enable transitions, allow-discrete, define timing */</span></span> <span class="line"><span style="color:var(--shiki-foreground)">[</span><span style="color:var(--shiki-token-function)">popover</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-string-expression)"> dialog</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-function)"> ::backdrop</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> display 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> overlay 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> opacity 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)">/* ON STAGE */</span></span> <span class="line"><span style="color:var(--shiki-foreground)">:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)">:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)">[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)">[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 1</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)">/* OFF STAGE */</span></span> <span class="line"><span style="color:var(--shiki-token-comment)">/* starting-style for pre-positioning (enter stage from here) */</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@starting-style</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> :</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> :</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> [</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> [</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>While this code is effective and terse, it&#39;s often not enough customization if you want to present and dismiss dialogs differently than you do popovers. Or make the entry animation different then the exit.</p> <h3> Transition a dialog <a name="transition-a-dialog" href="#transition-a-dialog">#</a> </h3> <p>Here&#39;s a <code>&lt;dialog&gt;</code> element with this barebones snippet applied. They look pretty terrible out of the box, but you can <a href="https://nerdy.dev/have-a-dialog">do amazing things with them</a>.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-dialog.mp4 width="1920" height="968" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-dialog.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/pen/OJeWWNZ">Try it</a></p> </figcaption> </figure> <p>To get started, a <code>&lt;dialog&gt;</code> element needs to be in the HTML. A <code>&lt;dialog&gt;</code> should be shown and hidden with buttons, one to show it can be in the page and one to close it should be inside the dialog.</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-token-function)"> onclick</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"demo.showModal()"</span><span style="color:var(--shiki-foreground)">>ā€¦&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">dialog</span><span style="color:var(--shiki-token-function)"> id</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"demo"</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">header</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-token-function)"> title</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"Close"</span><span style="color:var(--shiki-token-function)"> onclick</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"demo.close()"</span><span style="color:var(--shiki-foreground)">>close&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;/</span><span style="color:var(--shiki-token-string-expression)">header</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">dialog</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <q class="tip"> <p>You can enable <strong>light dismiss</strong> on a dialog and skip the close button like <code>&lt;dialog closedby=&quot;any&quot;&gt;</code></p> </q> <p>To animate the dialog transition:</p> <ol> <li>Two parts need animation: the <code>&lt;dialog&gt;</code> itself and its <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::backdrop"><code>::backdrop</code></a>.</li> <li>When a dialog is shown, <code>display: none</code> is changed to <code>display: block</code> and <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transition-behavior"><code>transition-behavior</code></a> enables timing this change with our animation.</li> <li>When a dialog is shown, it is cloned into the <a href="https://developer.mozilla.org/en-US/docs/Glossary/Top_layer">top layer</a>, this also needs to be timed with our animation.</li> <li>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open"><code>[open]</code></a> attribute is used to know when the dialog is open or closed. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style"><code>@starting-style</code></a> is used during the first render as starting styles.</li> </ol> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-string-expression)">dialog</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* Exit Stage To */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> translateY</span><span style="color:var(--shiki-token-constant)">(-20</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-token-constant)">)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;, &#x26;</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)">:backdrop {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition:</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> display 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> overlay 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> ease</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> ease</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* Exit Stage To */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* On Stage */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">] {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 1</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> translateY</span><span style="color:var(--shiki-token-constant)">(0</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-token-constant)">)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)">:backdrop {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity: 0.8</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* Enter Stage From */</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)"> @starting-style</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">] {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> translateY</span><span style="color:var(--shiki-token-constant)">(20</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-token-constant)">)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><a href="https://codepen.io/argyleink/pen/OJeWWNZ">Try it</a></p> <p>With this snippet as a starting point, you can find three popular dialog experiences for you to take code or inspiration from <a href="https://nerdy.dev/have-a-dialog">/have-a-dialog</a>.</p> <p>The following video shows the excellent keyboard experience. It also demonstrates the interruptible nature of a CSS transition, so a user can close it anytime they want and always see a smooth interface.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-dialogs.mp4 width="1920" height="1082" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-dialogs.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://nerdy.dev/have-a-dialog">nerdy.dev/have-a-dialog</a></p> </figcaption> </figure> <details class="resources"> <summary>Resources</summary> <ul> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement">https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement</a></li> <li><a href="https://nerdy.dev/notebook/dialog-starter.html">https://nerdy.dev/notebook/dialog-starter.html</a></li> <li><a href="https://www.youtube.com/watch?v=yORy1IEHDKk">https://www.youtube.com/watch?v=yORy1IEHDKk</a></li> <li><a href="https://web.dev/blog/baseline-entry-animations">https://web.dev/blog/baseline-entry-animations</a></li> <li><a href="https://www.youtube.com/watch?v=f3N-6MzK8Z0&list=PLNYkxOF6rcIDCWoS_GSIwA24gZcwtLCZj&index=1">Pop n&#39; Lock Dialog Mini Web Machine</a></li> </ul> </details> <h3> Transition a popover <a name="transition-a-popover" href="#transition-a-popover">#</a> </h3> <p>Like a <code>&lt;dialog&gt;</code> element, a popover appears over everything else in the top layer. Light dismiss is the default, and keyboard / focus management is all done for you.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-popover.mp4 width="1920" height="1082" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-popover.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/pen/mdZXzxW">https://codepen.io/argyleink/pen/mdZXzxW</a></p> </figcaption> </figure> <p><strong>Let&#39;s bulid it.</strong></p> <p>There&#39;s an HTML aspect to implementing the UX:</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-token-function)"> popovertarget</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"pop"</span><span style="color:var(--shiki-foreground)">>?&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">button</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-token-function)"> id</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"pop"</span><span style="color:var(--shiki-foreground)"> popover>An overlay with additional information.&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <p>Also, like a <code>&lt;dialog&gt;</code> element, to animate the transition of a popover&#39;s display property and insertion into the top layer, you need to combine <code>transition-behavior</code> and <code>@starting-style</code>.</p> <p>Notice with a popover, the open state isn&#39;t an attribute, it&#39;s a css pseudo-class <code>:popover-open</code>.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">[</span><span style="color:var(--shiki-token-function)">popover</span><span style="color:var(--shiki-foreground)">] {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;, &#x26;</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)">:backdrop {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition:</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> display 0.5</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> overlay 0.5</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity 0.5</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform 0.5</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* Exit Stage To */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* On Stage */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 1</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)">:backdrop {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity: 0.5</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* Enter Stage From */</span></span> <span class="line"><span style="color:var(--shiki-token-keyword)"> @starting-style</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-punctuation)">,</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-token-function)">::backdrop</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;:</span><span style="color:var(--shiki-token-string-expression)">popover-open</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transform</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> translateY</span><span style="color:var(--shiki-token-constant)">(10</span><span style="color:var(--shiki-token-keyword)">px</span><span style="color:var(--shiki-token-constant)">)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><a href="https://codepen.io/argyleink/pen/JjzqXee">Try it</a></p> <details class="resources"> <summary>Resources</summary> <ul> <li><a href="https://nerdy.dev/steal-this-popover-starter-kit">https://nerdy.dev/steal-this-popover-starter-kit</a></li> <li><a href="https://nerdy.dev/notebook/dialog-starter.html">https://nerdy.dev/notebook/dialog-starter.html</a></li> <li><a href="https://developer.chrome.com/blog/entry-exit-animations">https://developer.chrome.com/blog/entry-exit-animations</a></li> <li><a href="https://developer.chrome.com/blog/css-ui-ecommerce-popover">https://developer.chrome.com/blog/css-ui-ecommerce-popover</a></li> <li><a href="https://www.youtube.com/watch?v=ASb9vO3ARHo&list=PLNYkxOF6rcIDCWoS_GSIwA24gZcwtLCZj&index=5">Over-Easy Anchor + Popover Mini Web Machine</a></li> </ul> </details> <h2> Transition animation for <code>&lt;details&gt;</code> <a name="transition-animation-for-<code>&lt;details&gt;</code>" href="#transition-animation-for-<code>&lt;details&gt;</code>">#</a> </h2> <p><baseline-status featureId="interpolate-size"></baseline-status> <baseline-status featureId="transition-behavior"></baseline-status> <baseline-status featureId="details-content"></baseline-status></p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-details.mp4 width="1282" height="720" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-details.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p>Found on the <a href="https://chrome.dev/css-wrapped-2024/">CSS Wrapped 2024</a> website in the desktop layout.</p> </figcaption> </figure> <p>The disclosure element (<code>&lt;details&gt;</code>) has been waiting for CSS primitives to unlock its animation potential for many years.</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">>Show disclosed content&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">>ā€¦&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <p>The details element needs to transition to <code>height: auto</code> from <code>height: 0px</code> and a way to target the slotted content it internally uses for the disclosure. The new <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/interpolate-size"><code>interpolate-size</code></a> feature can be used for the height animation and <a href="https://developer.chrome.com/blog/styling-details"><code>::details-content</code></a> for the selector.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> inline-size</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 50</span><span style="color:var(--shiki-token-keyword)">ch</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">media</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">prefers-reduced-motion</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> no-preference</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> interpolate-size</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> allow-keywords</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;::</span><span style="color:var(--shiki-token-string-expression)">details-content</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> block-size</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 0</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> overflow-y</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> clip</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> transition</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> content-visibility 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> allow-discrete</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> opacity 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> block-size 1</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x26;[</span><span style="color:var(--shiki-token-function)">open</span><span style="color:var(--shiki-foreground)">]::</span><span style="color:var(--shiki-token-string-expression)">details-content</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> opacity</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> 1</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> block-size</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> auto</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><a href="https://codepen.io/argyleink/pen/QWewVjd">Try it</a></p> <details class="resources"> <summary>Resources</summary> <ul> <li><a href="https://developer.chrome.com/blog/styling-details">https://developer.chrome.com/blog/styling-details</a></li> <li><a href="https://nerdy.dev/open-and-close-transitions-for-the-details-element">https://nerdy.dev/open-and-close-transitions-for-the-details-element</a></li> <li><a href="https://css-tricks.com/almanac/pseudo-selectors/d/details-content/">https://css-tricks.com/almanac/pseudo-selectors/d/details-content/</a></li> </ul> </details> <h3> Bonus attribute <a name="bonus-attribute" href="#bonus-attribute">#</a> </h3> <p><baseline-status featureId="details-name"></baseline-status></p> <p>If you want to connect two or more details elements and have them close each other respectively, you can accomplish this with a shared <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#name"><code>name</code></a> attribute on each detail element you want to be connected. Very much like a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio">radio group</a>.</p> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-accordion.mp4 width="1280" height="720" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-accordion.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://developer.chrome.com/blog/styling-details">https://developer.chrome.com/blog/styling-details</a></p> </figcaption> </figure> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-token-function)"> name</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"linked-disclosure"</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">>Show disclosed content&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)"> &#x3C;</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">>ā€¦&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)">&#x3C;!-- name="linked-disclosure" connects these together --></span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">details</span><span style="color:var(--shiki-token-function)"> name</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"linked-disclosure></span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> &#x3C;summary>Show disclosed content&#x3C;/summary></span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> &#x3C;p>ā€¦&#x3C;/p></span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)">&#x3C;/details></span></span></code></pre> </code></pre> <h2> Animated adaptive gradient text <a name="animated-adaptive-gradient-text" href="#animated-adaptive-gradient-text">#</a> </h2> <p>A bold headline in a design is often complimented with a gradient, helping draw the eye with intrigue and vividness.</p> <p>Since 2015 the web has been able to create gradient text effects, and in the past 10 years, there have been some updates and enhancements: animation, user preferences and interpolation.</p> <ol> <li>Adapting the gradient text effect to <strong>light and dark themes</strong> is easy with the <code>prefers-color-scheme</code> query</li> <li>New animation updates enable gradient effects to <strong>go beyond spinning or moving gradient images around</strong>, but to change colors over time</li> <li>New interpolation updates allow those <strong>mixes to be more vivid, rich, and interesting</strong></li> </ol> <figure> <p><video src=https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-gradient-text.mp4 width="1920" height="1082" alt=" " poster="https://res.cloudinary.com/dnpmdb8r8/video/upload/so_0.5,f_auto,w_auto,q_auto/f_auto,q_auto/argyleink/css-2025-gradient-text.jpg" controls loop muted playsinline allowFullScreen /></p> <figcaption> <p><a href="https://codepen.io/argyleink/pen/vEBmZNw">https://codepen.io/argyleink/pen/vEBmZNw</a></p> </figcaption> </figure> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-1 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-2 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> color-change {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> to {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--_color-1-to)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-2</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--_color-2-to)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> ;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* light mode */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> yellow</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> orange</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> purple</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* dark mode */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">media</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> dark</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> lime</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> deeppink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1: var(--_color-1-from);</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-2: var(--_color-2-from);</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> animation: </span><span style="color:var(--shiki-token-string-expression)">color-change</span><span style="color:var(--shiki-foreground)"> 2s linear infinite alternate;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> background: linear-gradient(</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> to right var(--_space),</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> var(--color-1),</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> var(--color-2)</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> );</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* old browser support */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> -webkit-background-clip: text;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> -webkit-text-fill-color: transparent;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* modern browser version */</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> background-clip</span><span style="color:var(--shiki-foreground)">: text;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> color: transparent;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)"> @supports</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">in</span><span style="color:var(--shiki-token-constant)"> oklch</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff)</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space: in oklch;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>That&#39;s quite a snippet šŸ˜… How did it get to that?</p> <p>Most developers making a gradient text effect are starting here:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">to</span><span style="color:var(--shiki-token-constant)"> right</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> cyan)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-background-clip</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> text</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-text-fill-color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transparent</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <h3> remove the prefixes <a name="remove-the-prefixes" href="#remove-the-prefixes">#</a> </h3> <p>The first update or enhancement is to remove the prefixes. Although, so older browsers continue to support the effect, add the unprefixed values after:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">to</span><span style="color:var(--shiki-token-constant)"> right</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> cyan)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* old browser support */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-background-clip</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> text</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-text-fill-color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transparent</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* modern browser version */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> background-clip</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> text</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transparent</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <h3> Use updated gradient interpolation spaces <a name="use-updated-gradient-interpolation-spaces" href="#use-updated-gradient-interpolation-spaces">#</a> </h3> <p>Next, improve the quality of the gradient by progressively enhancing the <code>in</code> <a href="https://developer.chrome.com/docs/css-ui/access-colors-spaces#color_interpolation">interpolation syntax</a> with CSS variables and <code>@supports</code>.</p> <p>You could alternatively repeat the gradient definition and include <code>in oklch</code> in it, which would also work great and support older browsers.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> ;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-constant)"> background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">to</span><span style="color:var(--shiki-token-constant)"> right </span><span style="color:var(--shiki-token-function)">var</span><span style="color:var(--shiki-token-constant)">(--_space)</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> cyan)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* old browser support */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-background-clip</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> text</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> -webkit-text-fill-color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transparent</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* modern browser version */</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> background-clip</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> text</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> color</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> transparent</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">supports</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">in</span><span style="color:var(--shiki-token-constant)"> oklch</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff)</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> in oklch</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <h3> Create typed <code>&lt;color&gt;</code> properties <a name="create-typed-<code>&lt;color&gt;</code>-properties" href="#create-typed-<code>&lt;color&gt;</code>-properties">#</a> </h3> <p>For the gradient color animation use <code>@property</code>, like described in snippet #4. The typed color values can be animated inside of a gradient, like a gradient used with text.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-1 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-2 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>Now <code>--color-1</code> can be animated like <code>transition: ā€“color-1 .3s ease</code> or used in keyframes. These values that can animate, can be used anywhere color is allowed, like in a gradient text effect.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> color-change {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> to {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> lime --color-2: orange</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> animation</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> color-change 2</span><span style="color:var(--shiki-token-keyword)">s</span><span style="color:var(--shiki-token-constant)"> linear infinite alternate</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <h3> Make a few props, Swap em&#39; in a dark MQ <a name="make-a-few-props,-swap-em&#39;-in-a-dark-mq" href="#make-a-few-props,-swap-em&#39;-in-a-dark-mq">#</a> </h3> <p>To keep things declarative, I&#39;ve also defined color variables to hold the colors for animation.</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> yellow</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> orange</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> purple</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">media</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> dark</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> lime</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> deeppink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* set our typed variables to the "from" values */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1: var(--_color-1-from);</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-2: var(--_color-2-from);</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p>Put all those moments and reasons together, and we have arrived at the final snippet:</p> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-1 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@property</span><span style="color:var(--shiki-foreground)"> --color-2 {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> syntax: "&#x3C;color</span><span style="color:var(--shiki-token-keyword)">></span><span style="color:var(--shiki-foreground)">";</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> inherits: false;</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> initial-value</span><span style="color:var(--shiki-foreground)">: #000;</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)">@keyframes</span><span style="color:var(--shiki-foreground)"> color-change {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> to {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--_color-1-to)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-2</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> var</span><span style="color:var(--shiki-token-constant)">(--_color-2-to)</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-function)">.gradient-text</span><span style="color:var(--shiki-foreground)"> {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-foreground)"> ;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* light mode */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> yellow</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> orange</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> purple</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> hotpink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* dark mode */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> @</span><span style="color:var(--shiki-token-constant)">media</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">prefers-color-scheme</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> dark</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> lime</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-1-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-from</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> cyan</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_color-2-to</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-constant)"> deeppink</span><span style="color:var(--shiki-foreground)">;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-1: var(--_color-1-from);</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --color-2: var(--_color-2-from);</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> animation: </span><span style="color:var(--shiki-token-string-expression)">color-change</span><span style="color:var(--shiki-foreground)"> 2s linear infinite alternate;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-foreground)"> background: linear-gradient(</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> to right var(--_space),</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> var(--color-1),</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> var(--color-2)</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> );</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* old browser support */</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> -webkit-background-clip: text;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> -webkit-text-fill-color: transparent;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-comment)"> /* modern browser version */</span></span> <span class="line"><span style="color:var(--shiki-token-string-expression)"> background-clip</span><span style="color:var(--shiki-foreground)">: text;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> color: transparent;</span></span> <span class="line"></span> <span class="line"><span style="color:var(--shiki-token-keyword)"> @supports</span><span style="color:var(--shiki-foreground)"> (</span><span style="color:var(--shiki-token-constant)">background</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> linear-gradient</span><span style="color:var(--shiki-token-constant)">(</span><span style="color:var(--shiki-token-keyword)">in</span><span style="color:var(--shiki-token-constant)"> oklch</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff</span><span style="color:var(--shiki-token-punctuation)">,</span><span style="color:var(--shiki-token-constant)"> #fff)</span><span style="color:var(--shiki-foreground)">) {</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> --_space: in oklch;</span></span> <span class="line"><span style="color:var(--shiki-foreground)"> }</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <picture> <source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f913/512.webp" type="image/webp"> <img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f913/512.gif" alt="šŸ¤“" width="200" height="200"> </picture> <p>Some years these snippets are short and sweet, but not this year. Watch out for next year&#39;s article, who knows what you&#39;ll need to know!</p> Creating Static SVGs from GeoJSON - CSS In Real Life https://css-irl.info/creating-static-svgs-from-geojson/ 2025-01-17T17:17:09.412Z <p>Recently Iā€™ve been working with map data to create interactive visualisations. When working with maps itā€™s common to receive data as <a href="https://geojson.org/">GeoJSON</a>, a JSON format for encoding geographic features, which specifies the type of geometry and co-ordinates for the features we want to display on a map. Javascript mapping libraries such as <a href="https://docs.mapbox.com/mapbox-gl-js/api/">Mapbox GL</a> are designed to consume GeoJSON to render features on a canvas. Iā€™m fairly accustomed to using GeoJSON in this way ā€” for example, rendering geographic areas as different coloured polygons overlaid on a map to show varying values for different areas.</p> <p>But for a recent project I decided to take a different approach. Mapbox GL is a great library that provides a lot of useful features out of the box, like zooming and panning. Itā€™s also pretty hefty as far as libraries go, weighing in at <a href="https://bundlephobia.com/package/mapbox-gl@3.9.3">1.4MB un-minified</a>. This project did not require any advanced map functionality, however, and only required the map display to be centred on a particular area, with interactive features on hover.</p> <p>In the interests of minimising the JS payload for users, it made sense to render the map as a static SVG, with only minimal JS needed for interactivity. That meant I needed to convert the GeoJSON data I had been provided with to a static SVG file. In case you find yourself in a similar position, Iā€™m going to show you how to do this using <a href="https://d3js.org/">D3.js</a>. Thereā€™s a pre-prepared <a href="https://codepen.io/michellebarker/pen/pvzKVzK">example on Codepen</a>, in case you want to skip straight to the code.</p> <h2>Fetching the data</h2> <p>Weā€™ll use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> to fetch some hosted GeoJSON, which has the <code>.json</code> suffix. Iā€™ve uploaded an example file to Codepen, which has a limit of 5MB for file assets. In a real project, the GeoJSON file might be much bigger.</p> <p>Weā€™ll use the <code>json()</code> response method to parse our response data, just like any other JSON response, then weā€™ll log it to the console. We should see our parsed data.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> geojsonUrl <span class="token operator">=</span> <span class="token string">'https://assets.codepen.io/85648/map-example.json'</span> <span class="token function">fetch</span><span class="token punctuation">(</span>geojsonUrl<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre> <p>Depending on our geographic data, our GeoJSON could take different formats. In my case, the data I want to display is a <code>FeatureCollection</code>, consisting of several geographic areas. Alternatively you might have a single <code>Feature</code>, or geographic area, and that could consist of one or more polygons.</p> <p>Here is an example of a very simple GeoJSON feature. The <code>geometry</code> type is <code>Point</code>, which means it pinpoints a specific location ā€” useful if youā€™re adding a marker to a map, for instance. For drawing geographic areas, the <code>geometry</code> type will likely be <code>Polygon</code> or <code>MultiPolygon</code>.</p> <pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Feature"</span><span class="token punctuation">,</span> <span class="token property">"geometry"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"Point"</span><span class="token punctuation">,</span> <span class="token property">"coordinates"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">125.6</span><span class="token punctuation">,</span> <span class="token number">10.1</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"properties"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Example location"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <h2>Creating the SVG</h2> <p>Before rendering our data as an SVG path, we first need to create an empty SVG element. We <em>could</em> do this in HTML:</p> <pre class="language-html"><code 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">"</span>600<span class="token punctuation">"</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">"</span>400<span class="token punctuation">"</span></span> <span class="token attr-name">viewBox</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>0 0 600 400<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre> <p>However, since weā€™re going to be using D3 anyway, letā€™s create the SVG in Javascript, the D3 way. That makes it simple to set our SVG dimensions as variables, which weā€™ll use again shortly.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> dimensions <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">600</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token punctuation">}</span></code></pre> <p>It also means we can wait until <em>after</em> the browser has successfully fetched our data and parsed the response before rendering the SVG ā€” and gives us the option of showing a helpful error message to users in case our request fails.</p> <p>Weā€™ll use D3ā€™s <code>select()</code> method to select an element to which to append our SVG. This could be the <code>&lt;body&gt;</code>, or any other element. In this case, weā€™ll use a <code>&lt;div&gt;</code> with an ID of <code>wrapper</code>.</p> <p>Weā€™ll append an SVG element, then set the width, height and viewBox attributes.</p> <pre class="language-js"><code class="language-js"><span class="token function">fetch</span><span class="token punctuation">(</span>geojsonUrl<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> d3<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token string">'#wrapper'</span><span class="token punctuation">)</span> <span class="token comment">// The element which to append our SVG to</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'svg'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'width'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>width<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'viewBox'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre> <h2>Converting GeoJSON to an SVG path</h2> <p>Now weā€™re going to use D3ā€™s <a href="https://d3js.org/d3-geo/path">geographic path generator</a> to generate SVG path strings from our data. Weā€™ll append a <code>path</code> element to the SVG and set the <code>d</code> attribute (the instructions for how to draw the line) from our data. The <code>geoPath()</code> function can render a path from a single feature or geometry, or from multiple features combined into a <code>FeatureCollection</code>. If we have a single feature we can create a path generator, and call it with data:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> path <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">geoPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span> d3<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token string">'#wrapper'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'svg'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'width'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>width<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'viewBox'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span> <span class="token comment">// Append a 'path' element</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'d'</span><span class="token punctuation">,</span> <span class="token function">path</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Draw the path from the data</span></code></pre> <p>If our data consists of a <code>FeatureCollection</code>, we might instead need to render multiple paths. We approach this slightly differently, by binding the dataset to our SVG, then appending a path for each of the features in the <code>FeatureCollection</code>.</p> <pre class="language-js"><code class="language-js">d3<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token string">'#wrapper'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'svg'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'width'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>width<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'height'</span><span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'viewBox'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dimensions<span class="token punctuation">.</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">selectAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">data</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>features<span class="token punctuation">)</span> <span class="token comment">// Bind the features in the FeatureCollection</span> <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'path'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">attr</span><span class="token punctuation">(</span><span class="token string">'d'</span><span class="token punctuation">,</span> path<span class="token punctuation">)</span> <span class="token comment">// We donā€™t need to call `path` with an argument, as weā€™ve already bound the data</span></code></pre> <p>This should create paths from our data. It also works if our data contains ā€œMultiPolygonsā€ ā€” multidimensional arrays of polygons. Note, we could alternatively use only the <code>geometry</code> data from our features instead of the entire <code>Feature</code> object.</p> <h3>Projection and scaling</h3> <p>Although inspecting the SVG element in the browser might show that weā€™ve rendered some SVG paths, itā€™s likely theyā€™ll currently be invisible to the viewer. Thatā€™s because we havenā€™t yet scaled them to our SVG viewbox, so they may be rendered off-canvas. We need to tell D3 how to project our map elements onto the available space.</p> <p>For this weā€™ll transform the <a href="https://d3js.org/d3-geo/projection">projection</a>, by calling <a href="https://d3js.org/d3-geo/projection#geoIdentity">geoIdentity()</a>, using the <code>fitSize()</code> method to scale it to our SVG bounding box. Our revised projection is passed in as an argument to <code>d3.geoPath()</code>, overriding the default projection.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> projection <span class="token operator">=</span> d3 <span class="token punctuation">.</span><span class="token function">geoIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">fitSize</span><span class="token punctuation">(</span><span class="token punctuation">[</span>dimensions<span class="token punctuation">.</span>width<span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">]</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token keyword">const</span> path <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">geoPath</span><span class="token punctuation">(</span>projection<span class="token punctuation">)</span></code></pre> <p>This assumes the top left SVG co-ordinates should be [0, 0] ā€” otherwise you should use <code>fitExtent()</code> which allows us to specify all corners of the bounding box.</p> <h3>Flipping the path</h3> <p>Now our paths should render visibly. But you might notice thereā€™s one more issue: they are upside-down. Be careful because this might not be totally obvious with unfamiliar paths. But it was certainly noticeable with a map of the UK!</p> <p>The reason for this is that standard spatial reference systems treat the <em>y</em> axis as pointing upwards from 0, while in the SVG co-ordinate system the <em>y</em> axis points downwards, with 0 at the top. Luckily D3 provides a method for reflecting our projection in the <em>y</em> dimension.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> projection <span class="token operator">=</span> d3 <span class="token punctuation">.</span><span class="token function">geoIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">reflectY</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment">// Flip the paths in the y dimension</span> <span class="token punctuation">.</span><span class="token function">fitSize</span><span class="token punctuation">(</span><span class="token punctuation">[</span>dimensions<span class="token punctuation">.</span>width<span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">]</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token keyword">const</span> path <span class="token operator">=</span> d3<span class="token punctuation">.</span><span class="token function">geoPath</span><span class="token punctuation">(</span>projection<span class="token punctuation">)</span></code></pre> <h2>Result</h2> <p>Check out the Codepen demo below to see this in action. You can replace the <code>geojsonUrl</code> variable with your own GeoJSON data URL to create an SVG from your own data.</p> <p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="pvzKVzK" data-pen-title="GeoJSON to SVG" data-user="michellebarker" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="https://codepen.io/michellebarker/pen/pvzKVzK"> GeoJSON to SVG</a> by Michelle Barker (<a href="https://codepen.io/michellebarker">@michellebarker</a>) on <a href="https://codepen.io/">CodePen</a>.</span> </p> <script async="" src="https://public.codepenassets.com/embed/index.js"></script> <p>Once I created this I was able to copy the resulting SVG code and save the static file for use in my codebase.</p> <h2>Common issues drawing paths from GeoJSON</h2> <p>When working with GeoJSON polygon data (particularly with map libraries) I sometimes get an error along the lines of ā€œPolygons and MultiPolygons should follow the right-hand ruleā€. This tends to occur in GeoJSON validators, or when using a library like Mapbox. (I didnā€™t have this issue with the above code.) This relates to the GeoJSON specification regarding how polygons are ā€œdrawnā€. It states that ā€œA linear ring MUST follow the right-hand rule with respect to the area it bounds, i.e. exterior rings are counterclockwise, and holes are clockwise.ā€</p> <p>There are a couple of ways to fix this:</p> <ol> <li>In the browser, by uploading the file or pasting the code into the <a href="https://mapstertech.github.io/mapster-right-hand-rule-fixer/">mapster-right-hand-rule-fixer</a> tool.</li> <li>Using Mapboxā€™s <a href="https://github.com/mapbox/geojson-rewind">rewind</a> package,</li> </ol> <p>Both of these will output the polygons in the correct format.</p> <h2>Server-side generation</h2> <p>After implementing this in the browser I got curious about generating SVGs from GeoJSON at build-time. This didnā€™t take too much work, and allows me to easily update the SVG if the data changes.</p> <p>We need to do this slightly differently as there is no DOM, so we canā€™t select elements using D3. But we can still generate the paths easily, append them to an SVG element and write it to a file.</p> <p>We can still use the <code>geoPath()</code> and <code>geoIdentity()</code> methods as previously. This time, however, weā€™ll map over the features and return a HTML string. In addition the the <code>d</code> attribute, Iā€™m giving each path a unique ID based on its properties, which will be useful for interaction.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> projection <span class="token operator">=</span> <span class="token function">geoIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">reflectY</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">fitSize</span><span class="token punctuation">(</span><span class="token punctuation">[</span>dimensions<span class="token punctuation">.</span>width<span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">]</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">geoPath</span><span class="token punctuation">(</span>projection<span class="token punctuation">)</span> <span class="token keyword">const</span> paths <span class="token operator">=</span> data<span class="token punctuation">.</span>features<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">d</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;path id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>d<span class="token punctuation">.</span>properties<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" d="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">path</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" /></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre> <p>Then we just need to append those paths to the SVG element and write to a file using Node JSā€™s <a href="https://nodejs.org/api/fs.html#fspromiseswritefilefile-data-options">writeFile()</a> method.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> fileData <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;svg width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" height="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" viewBox="0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>paths<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/svg></span><span class="token template-punctuation string">`</span></span> <span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token string">'./src/map-svg.svg'</span><span class="token punctuation">,</span> fileData<span class="token punctuation">)</span></code></pre> <p>Hereā€™s the full code:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> geoPath<span class="token punctuation">,</span> geoIdentity <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'d3'</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> writeFile <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:fs/promises'</span> <span class="token keyword">const</span> geojsonUrl <span class="token operator">=</span> <span class="token string">'https://assets.codepen.io/85648/map-example.json'</span> <span class="token keyword">const</span> dimensions <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">600</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">300</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token function">fetch</span><span class="token punctuation">(</span>geojsonUrl<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">response</span><span class="token punctuation">)</span> <span class="token operator">=></span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'āœØGenerating SVG'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> width<span class="token punctuation">,</span> height <span class="token punctuation">}</span> <span class="token operator">=</span> dimensions <span class="token keyword">const</span> projection <span class="token operator">=</span> <span class="token function">geoIdentity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">reflectY</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment">// SVG co-ordinate system is the opposite way up, so we need to flip it</span> <span class="token punctuation">.</span><span class="token function">fitSize</span><span class="token punctuation">(</span><span class="token punctuation">[</span>dimensions<span class="token punctuation">.</span>width<span class="token punctuation">,</span> dimensions<span class="token punctuation">.</span>height<span class="token punctuation">]</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token comment">// Scale to fit our SVG dimensions</span> <span class="token keyword">const</span> path <span class="token operator">=</span> <span class="token function">geoPath</span><span class="token punctuation">(</span>projection<span class="token punctuation">)</span> <span class="token keyword">const</span> paths <span class="token operator">=</span> data<span class="token punctuation">.</span>features<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">d</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;path id="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>d<span class="token punctuation">.</span>properties<span class="token punctuation">.</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" d="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">path</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" /></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">const</span> fileData <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;svg xmlns="http://www.w3.org/2000/svg" width="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" height="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">" viewBox="0 0 </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>width<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>height<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>paths<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> &lt;/svg></span><span class="token template-punctuation string">`</span></span> <span class="token keyword">await</span> <span class="token function">writeFile</span><span class="token punctuation">(</span><span class="token string">'./src/map-svg.svg'</span><span class="token punctuation">,</span> fileData<span class="token punctuation">)</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Done!'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'Error writing file'</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 <a href="https://gist.github.com/mbarker84/de0f5493a3d7b69f656682f9724b34d2">Github gist with this code</a></p> Advanced Attr - Adam Argyle https://nerdy.dev/advanced-attr?utm_source=rss 2025-01-17T16:52:05.004Z <video style="display: none" src="https://res.cloudinary.com/dnpmdb8r8/video/upload/f_auto,w_auto,q_auto/argyleink/advanced-attr.gif" alt="code example video showing the old and the new way compared" height="470" width="1418" /> <p>Advanced <code>attr()</code> in <a href="https://developer.chrome.com/blog/chrome-133-beta">Chrome 133</a>. Opening new ways to integrate with CSS from your HTML:</p> <pre><code class="language-html"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">li</span><span style="color:var(--shiki-token-function)"> scroll-snap</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"start"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">li</span><span style="color:var(--shiki-foreground)">></span></span> <span class="line"><span style="color:var(--shiki-foreground)">&#x3C;</span><span style="color:var(--shiki-token-string-expression)">li</span><span style="color:var(--shiki-token-function)"> scroll-snap</span><span style="color:var(--shiki-foreground)">=</span><span style="color:var(--shiki-token-string-expression)">"center"</span><span style="color:var(--shiki-foreground)">>&#x3C;/</span><span style="color:var(--shiki-token-string-expression)">li</span><span style="color:var(--shiki-foreground)">></span></span></code></pre> </code></pre> <pre><code class="language-css"><pre class="shiki css-variables" style="background-color:var(--shiki-background);color:var(--shiki-foreground)" tabindex="0"><code><span class="line"><span style="color:var(--shiki-foreground)">[</span><span style="color:var(--shiki-token-function)">scroll-snap</span><span style="color:var(--shiki-foreground)">] {</span></span> <span class="line"><span style="color:var(--shiki-token-constant)"> scroll-snap-align</span><span style="color:var(--shiki-token-keyword)">:</span><span style="color:var(--shiki-token-function)"> attr</span><span style="color:var(--shiki-token-constant)">(scroll-snap type(*)</span><span style="color:var(--shiki-foreground)">);</span></span> <span class="line"><span style="color:var(--shiki-foreground)">}</span></span></code></pre> </code></pre> <p><a href="https://codepen.io/argyleink/pen/qEWyZgx">Try it</a> &amp; learn more from <a href="http://bram.us">Bramus</a> @ <a href="https://developer.chrome.com/blog/advanced-attr">Chrome Developers</a></p>