CSS and design systems people - BlogFlock2025-03-15T14:42:24.032ZBlogFlockMichelle Barker, Hidde de Vries, Robin Rendle, Stephanie Eckles, Sara Joy, Sara Soueidan, Robin Rendle, Adam ArgyleLet the stagger experiments begin - Adam Argylehttps://nerdy.dev/sibling-index?utm_source=rss2025-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'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)"><</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)"> <</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)">></</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)"> <</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)">></</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)"> <</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)">></</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)"> <</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)">></</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)"> <</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)">></</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)"></</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)"><</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)"> <</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></</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)"> <</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></</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)"> <</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></</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)"> <</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></</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)"> <</span><span style="color:var(--shiki-token-string-expression)">div</span><span style="color:var(--shiki-foreground)">></</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)"></</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'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 Rendlehttps://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 bloghttps://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 Argylehttps://nerdy.dev/view-transitions-3d-perspective?utm_source=rss2025-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 Argylehttps://nerdy.dev/css-kaleidoscopes?utm_source=rss2025-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'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'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's has a ton of fun levers to pull in the CSS. Def try changing
variables, durations, easings, and more.</p>
<p><strong>It'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'll add it here!</p>
An Emoji Naming Convention - Adam Argylehttps://nerdy.dev/css-emoji-convention?utm_source=rss2025-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's unique in a contextā¦ and that's really hard to remember.</p>
<p>Here'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'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'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'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'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)"> < 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'll add them!</p>
<p>As a final mention here, I think it's a good idea to <code>--</code> all your custom names, just to make it really clear they aren't internal CSS keywords. </p>
Anchoreum - Adam Argylehttps://nerdy.dev/anchoreum?utm_source=rss2025-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 Argylehttps://nerdy.dev/39-today?utm_source=rss2025-02-19T19:06:43.139Z<p>39 today, wheeeeā¦</p>
trot - Robin Rendlehttps://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><input></code> or a <code><button></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 Lifehttps://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 Lifehttps://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 Argylehttps://nerdy.dev/scroll-driven-animations-notebook?utm_source=rss2025-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'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 Argylehttps://nerdy.dev/new-404-page?utm_source=rss2025-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 Argylehttps://nerdy.dev/new-footer-went-big?utm_source=rss2025-02-03T00:38:11.655Z<p>New <code><footer></code>,<br> Went big.</p>
Notes on blogging - Robin Rendlehttps://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 Argylehttps://nerdy.dev/bad-at-css-with-lane-wagner?utm_source=rss2025-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 Rendlehttps://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 Argylehttps://nerdy.dev/6-css-snippets-every-front-end-developer-should-know-in-2025?utm_source=rss2025-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><dialog></code></a>, <a href="#transition-a-popover">popover</a>, and
<a href="#transition-animation-for-%3Cdetails%3E"><code><details></code></a>, animate light n' 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 "curves", 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'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's what your formatter will do to it, as if it'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'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'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><color></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: "<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's simple.</p>
<p>In 2025, y'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&t=3409s">https://www.youtube.com/watch?v=tSfSY3Ni3X0&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'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'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'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'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><a></code> and an <code><h1></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><dialog></code> and <code>[popover]</code>
<a name="transition-animation-for-<code><dialog></code>-and-<code>[popover]</code>" href="#transition-animation-for-<code><dialog></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><dialog></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><dialog></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'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's a <code><dialog></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><dialog></code> element needs to be in the HTML. A <code><dialog></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)"><</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)">>ā¦</</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)"><</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)"> <</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)"> <</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</</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)"> </</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)"></</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><dialog closedby="any"></code></p>
</q>
<p>To animate the dialog transition:</p>
<ol>
<li>Two parts need animation: the <code><dialog></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)"> &, &</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)"> &[</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)"> &</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)"> &[</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>
<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>
<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' 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><dialog></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's bulid it.</strong></p>
<p>There'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)"><</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)">>?</</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)"><</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.</</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><dialog></code> element, to animate the transition of a popover'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't an attribute, it'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)"> &, &</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)"> &:</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)"> &</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)"> &:</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-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)"> &:</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><details></code>
<a name="transition-animation-for-<code><details></code>" href="#transition-animation-for-<code><details></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><details></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)"><</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)"> <</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">>Show disclosed content</</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)"> <</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">>ā¦</</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)"></</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)"> &::</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)"> &[</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)"><</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)"> <</span><span style="color:var(--shiki-token-string-expression)">summary</span><span style="color:var(--shiki-foreground)">>Show disclosed content</</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)"> <</span><span style="color:var(--shiki-token-string-expression)">p</span><span style="color:var(--shiki-foreground)">>ā¦</</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)"></</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)"><!-- name="linked-disclosure" connects these together --></span></span>
<span class="line"></span>
<span class="line"><span style="color:var(--shiki-foreground)"><</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)"> <summary>Show disclosed content</summary></span></span>
<span class="line"><span style="color:var(--shiki-token-string-expression)"> <p>ā¦</p></span></span>
<span class="line"><span style="color:var(--shiki-token-string-expression)"></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: "<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: "<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'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><color></code> properties
<a name="create-typed-<code><color></code>-properties" href="#create-typed-<code><color></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: "<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: "<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' in a dark MQ
<a name="make-a-few-props,-swap-em'-in-a-dark-mq" href="#make-a-few-props,-swap-em'-in-a-dark-mq">#</a>
</h3>
<p>To keep things declarative, I'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: "<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: "<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's article, who knows what you'll need to know!</p>
Creating Static SVGs from GeoJSON - CSS In Real Lifehttps://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"><</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"></</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><body></code>, or any other element. In this case, weāll use a <code><div></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"><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"><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"></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"><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"><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">
</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 Argylehttps://nerdy.dev/advanced-attr?utm_source=rss2025-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)"><</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)">></</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)"><</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)">></</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> & learn more from
<a href="http://bram.us">Bramus</a> @
<a href="https://developer.chrome.com/blog/advanced-attr">Chrome Developers</a></p>