<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Geoff Rich</title>
	<subtitle></subtitle>
	<link href="https://geoffrich.net/feed.xml" rel="self"/>
	<link href="https://geoffrich.net/"/>
	
	<updated>2024-06-21T00:00:00Z</updated>
	
	<id>https://geoffrich.net</id>
	<author>
  <name>Geoff Rich</name>
  <email>no@email</email>
	</author>
	
  
  
  
  
  <entry>
    <title>CascadiaJS 2024: Optimize for vibes</title>
    <link href="https://geoffrich.net/posts/cascadiajs-2024/"/>
    <updated>2024-06-21T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/cascadiajs-2024/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="PQluuawldDE" style="background-image: url('https://i.ytimg.com/vi/PQluuawldDE/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Optimize for Vibes: Svelte 5 and the New Age of Svelte</span>
  </button>
</lite-youtube>
<p>I gave a talk at <a href="https://cascadiajs.com/2024">CascadiaJS 2024</a> on what's new in Svelte 5. What follows is a full transcript of the talk. You can also grab <a href="https://drive.google.com/drive/folders/14EgoTzgKL_PCmtp8pRQEv2e-99RenKbw">the slides</a> (Keynote and PDF available).</p>
<p>I gave this talk before Svelte 5 <a href="https://github.com/sveltejs/svelte/releases/tag/svelte%405.0.0">was officially released</a>, but it should still be largely applicable.</p>
<blockquote>
<p>Let's look at what's new in Svelte 5 and how it makes Svelte apps more performant, more scalable, and easier to reason about. We'll also unpack why the changes were made and how they were rolled out.</p>
</blockquote>
<hr>
<p>My name is Geoff. Svelte is a JavaScript framework that I contribute to, and today I’m going to talk about what’s changing in the next major version of Svelte and why.</p>
<p>But first — there’s a good chance that some of you have never heard of Svelte. So what is Svelte?</p>
<p>In one sentence, Svelte is <strong>a compiled, batteries-included, HTML-first JavaScript framework that prioritizes vibes</strong>.</p>
<p>Let's break that down.</p>
<p>If you know anything about Svelte, it’s probably that it’s a <strong>compiler</strong>. A compiler is a program that takes code written in one programming language and transforms it into another language. In Svelte’s case, it takes Svelte component code — which looks similar to HTML — and transforms it into tightly-optimized vanilla JavaScript.</p>
<p>This was a super novel approach to front-end frameworks when Svelte first came out in 2016; now having a compiler in your framework is less unique. SolidJS uses a compiler in ways similar to Svelte but uses JSX as the template language of choice. Vue has had compiler-driven optimizations for a while and is working on an experimental “vapor mode” that drops the virtual DOM completely in favor of a compiler. And last month React open sourced their experimental compiler “React forget” so you can stop hand-optimizing your React component rendering.</p>
<p>Something that I think makes Svelte special is that we are compiler-first and compiler-only. Compilation is not a separate mode for us; it is a fundamental of the framework. Svelte components don’t need to run in the browser as-is. So we can start with what we want the authoring experience to be, and use the compiler to make that possible. This results in component code with very minimal boilerplate, and also lets us make a ton of performance optimizations that result in small, fast web apps.</p>
<p>Svelte also aims to to be <strong>batteries-included</strong>. Instead of only being a component framework, we also provide solutions to common web-app concerns, like scoped styling, input bindings, intro and outro animations, accessibility warnings, and even spring physics.</p>
<p>Svelte is also <strong>HTML first</strong>. We think HTML is a pretty good language for describing UI, and want Svelte to feel like a reactive extension on top of HTML, not JavaScript. Whereas React starts with JavaScript and adds HTML to it with JSX, Svelte takes HTML and enhances it with JavaScript reactivity.</p>
<p>We also prioritize Svelte’s unique <strong>vibes</strong>. Being small and fast is great, and those are priorities for us — but what we optimize for is how it feels to write Svelte and the aesthetics of it. And vibes is something we’re going to come back to as we talk about the next major version of Svelte and what’s changing, because I think optimizing for vibes is a key part of successfully making a breaking change.</p>
<p>Part of making a change like this — and I’ll get to exactly what that change is in a second — is making sure that the project after the change still feels like the same project. People get attached to the vibes of the framework they use. If you’re changing your framework, you want the vibes to be the same, or at least similar.</p>
<p>Because if you make a breaking change and it feels like a completely different framework — well, maybe you should have just made a new framework? Despite the significant changes coming in Svelte 5, we still think it feels like Svelte, and we hope our users do too.</p>
<p>In case you haven’t used Svelte before, here’s what a component currently looks like in Svelte 4. A lot of this will be changing in Svelte 5, but the shape of a Svelte component will remain — and less may change than you might think. So, Svelte components look like HTML. Here we have a simple counter component</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    <span class="token keyword">export</span> <span class="token keyword">let</span> text <span class="token operator">=</span> <span class="token string">'Click me!'</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>    <span class="token literal-property property">$</span><span class="token operator">:</span> doubled <span class="token operator">=</span> count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br>    <span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'more than 10'</span><span class="token punctuation">)</span><br>    <span class="token punctuation">}</span><br><br>    <span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        count<span class="token operator">++</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span>increment<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>text<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Count: <span class="token language-javascript"><span class="token punctuation">{</span>count<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Doubled: <span class="token language-javascript"><span class="token punctuation">{</span>doubled<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>Down below you have the template for your component, and the logic lives in the script tag. You can also have a style tag containing scoped styles for the component, but we’re going to focus on the script section today.</p>
<p>On the first line we use <code>export let</code> to define a component <strong>prop</strong>, in this case a “text” prop to set the counter button’s text.</p>
<p>We can also define a state variable with let, just like you would declare a regular JavaScript variable. In this case, we’re defining a piece of state called count that we use in the template by wrapping it in curly braces.</p>
<p>Importantly, and what a lot of people like about Svelte, is that you can update that state variable directly, without needing to use a setCount function or other framework abstraction. When we call count++ in the <code>increment</code> function, the DOM will update with the new value of count.</p>
<p>You can also define <strong>derived values</strong> using the <code>$:</code> label syntax. In this case, we’re creating a variable called doubled, and saying that it should always be two times the value of count. Whenever we update count, Svelte will automatically update the value of doubled as well.</p>
<p>Finally, <code>$:</code> also defines a <strong>reactive block</strong>, where you can run some code after any of the state variables referenced in the block change. In this case, whenever <code>count</code> is updated, this block will run and log a message if count is greater than 10</p>
<p>So, Svelte works great today, and a lot of people love Svelte.</p>
<p>However, the core of Svelte hasn’t really changed in 5 years, since <a href="https://svelte.dev/blog/svelte-3-rethinking-reactivity">Svelte 3’s release</a> in 2019. And so as the current API got more and more usage, we noticed some areas of improvement.</p>
<p>5 years is also a long time in the web dev world. 5 years ago:</p>
<ul>
<li>React had just released hooks</li>
<li>If you were writing React, you were probably using create-react-app. That app might <em>still</em> be using create-react-app</li>
<li>Vercel was called ZEIT</li>
<li>Vue was still on version 2, and only just proposing what would become the composition API</li>
<li>SolidJS wasn’t well-known yet</li>
<li>Microsoft Edge was still its own browser engine instead of being Chromium in a trenchcoat</li>
<li>If you were unlucky, you still had to support IE 11</li>
</ul>
<p>Think of how much frontend frameworks and the frontend in general has changed since then. With Svelte 5, we’re taking advantage of all the learnings and innovations that other frameworks have made in the last 5 years, and using them to make Svelte even better than it already is.</p>
<p>So what does it mean to make Svelte better?</p>
<p>Summarized, Svelte 5’s main goal is <strong>universal, runtime reactivity.</strong> With this change, we hope to make Svelte more <strong>scalable</strong> and able to tackle projects of any size, and also make Svelte’s mental model more <strong>consistent</strong> and easier to learn.</p>
<h2>universal reactivity</h2>
<p>Let’s start with universal reactivity. Today, Svelte has two reactivity models. There’s the easy-to-use reactivity model inside Svelte components, but that reactivity model doesn’t work if we try to move it to a separate file. Let’s take another look at the example I showed.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>    <span class="token literal-property property">$</span><span class="token operator">:</span> doubled <span class="token operator">=</span> count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br>    <span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'more than 10'</span><span class="token punctuation">)</span><br>    <span class="token punctuation">}</span><br><br>    <span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        count<span class="token operator">++</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>Let’s say you wanted to wrap up the “counter” reactivity code into a utility that could be used anywhere. You can’t just copy paste the code into a JS file or even move it into a function inside the component — Svelte’s reactivity model only works at the top level of Svelte components. Instead, to move it we need to rewrite to a completely different reactivity API — stores.</p>
<p>Here’s what that might look like. Don’t worry about understanding this code — the details are beside the point.</p>
<pre><code class="language-js">
<mark>import { writable, derived, readonly } from 'svelte/store';
import { onDestroy } from 'svelte';</mark>

export <mark>function</mark> createCounter() {
    let count = <mark>writable(0);</mark>
    <mark>let</mark> doubled = <mark>derived(count, $count => $</mark>count * 2<mark>)</mark>;

    <mark>let unsub = count.subscribe($count => {</mark>
        if ($count > 10) {
            console.log('more than 10');
        }
    <mark>})</mark>

    <mark>onDestroy(unsub);</mark>

    function increment() {
        count<mark>.update(n => n + 1);</mark>
    }

    return { count: readonly(count), doubled, increment };
}
</code></pre>
<p>Just notice what I’ve highlighted — this is what you had to change in order to extract the code from the component. That’s a lot to change just so you could share this reactivity between multiple components. And because that makes refactoring difficult, Svelte components often grow larger than necessary.</p>
<p>But what if you could just copy-paste your reactivity code into a different file and it would work? What if we could use Svelte’s component reactivity model everywhere? Well — that’s universal reactivity in Svelte 5. But before showing what that looks like, let’s discuss the other big change.</p>
<h2>runtime reactivity</h2>
<p>Svelte 5 also moves from <strong>compiler-based</strong> reactivity to <strong>runtime</strong> reactivity.</p>
<p>Svelte 4’s reactivity uses compile-time static analysis. The compiler analyzes your code to determine which variables you’re updating where, and then adds code to invalidate data at the right time. This works well most of the time, but there are some extremely tricky edge cases that are hard to solve with a compiler based model. It makes other kinds of refactoring tricky as well.</p>
<p>For instance, looking at the component code from earlier, let’s say that doubling a number was a verbose operation that we wanted to move into its own function. In Svelte 4, if you do this, you’ll actually break Svelte’s reactivity and doubled will no longer respond to changes in count. Doubled’s dependency on count is now invisible to the compiler.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// this doesn't work!</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> doubled <span class="token operator">=</span> <span class="token function">timesTwo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">function</span> <span class="token function">timesTwo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">return</span> count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Instead, in Svelte 4, you need to pass count as an argument so that the compiler understands that this function should re-run when count changes. But it’s unintuitive and easy to miss a variable when refactoring large reactive blocks.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// instead, do this</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> doubled <span class="token operator">=</span> <span class="token function">timesTwo</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">function</span> <span class="token function">timesTwo</span><span class="token punctuation">(</span><span class="token parameter">num</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">return</span> num <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>So in Svelte 5, the compiler is no longer responsible for determining reactive dependencies. Instead, those are determined when the code runs in the browser. This doesn’t mean Svelte is dropping the compiler — far from it. It’s just not using it for reactive dependency tracking anymore. We’ll talk more about the implications of this later. So, this is what Svelte 5 delivers — universal, runtime reactivity. Now let’s see what that looks like in practice. It’s time to talk runes.</p>
<h2>runes</h2>
<p>Runes are the new way to express reactivity in Svelte 5. That same Svelte 4 component we looked at, would look like this in the new reactivity system:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    <span class="token keyword">let</span> <span class="token punctuation">{</span> text <span class="token operator">=</span> <span class="token string">'Click me!'</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">$props</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">let</span> doubled <span class="token operator">=</span> <span class="token function">$derived</span><span class="token punctuation">(</span>count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token function">$effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>        <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>            console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'more than 10'</span><span class="token punctuation">)</span><br>        <span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        count<span class="token operator">++</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>Let's break this down.</p>
<p>In Svelte 5, those function-like symbols starting with a dollar sign are called runes. But they’re not actually functions and you don’t import them to use them. Think of them as instructions to Svelte’s compiler. These runes replace Svelte’s existing reactivity syntax.</p>
<ul>
<li>instead of export let, props are now destructured from a $props rune</li>
<li>state variables are now declared with the $state rune. Under the hood, the $state rune will use something called a signal so Svelte can perform targeted updates when that value changes. However, as far as the component author is concerned, “count” is just a regular number, and you can access and update it just like you would any number in JavaScript. Count is not a special reactive object, or a function that needs to be called — it’s just a number. This is important for vibes reasons, and we’ll talk more about it later.</li>
<li>instead of $:, derived values now use the $derived rune.</li>
<li>and our reactive block turns into an $effect rune, which will re-run whenever any of the reactive variables inside it change.</li>
</ul>
<p>On the surface, this may look like mostly a syntactical change. Let’s talk about how this solves some of the issues from earlier. First, unlike the $ label, $derived and $effect use runtime reactivity.</p>
<p>This means we can now safely refactor into functions without breaking anything. If you’re learning Svelte today, you don’t need to learn the gotcha about passing in reactive dependencies as arguments.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token comment">// this works now!</span><br>    <span class="token keyword">let</span> doubled <span class="token operator">=</span> <span class="token function">$derived</span><span class="token punctuation">(</span><span class="token function">timesTwo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">function</span> <span class="token function">timesTwo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        <span class="token keyword">return</span> count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>With these runes, we now also have universal reactivity, which means it’s much easier to refactor this reactive counter into a separate file if we wanted to share between components. Earlier we showed how to do this in Svelte 4, and it involved rewriting to a completely different, more verbose reactivity API called “stores”. In Svelte 5, not only is the reactive logic more concise than the store version, it can be copied in a function verbatim.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">createCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> doubled <span class="token operator">=</span> <span class="token function">$derived</span><span class="token punctuation">(</span>count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token function">$effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'more than 10'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    count<span class="token operator">++</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token keyword">get</span> <span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">return</span> count<span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token keyword">get</span> <span class="token function">doubled</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">return</span> doubled<span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">,</span><br>    increment<br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>There’s no need to convert the code to the store API. The only change is that the function needs to return data for the component to use.</p>
<p>Let’s take a closer look at what we’re returning here, because that may look unfamiliar to some of you.</p>
<p>Those function-looking values for “count” and “doubled” are called getters. In case you haven’t seen that syntax before — it’s not Svelte-specific, it’s Just JavaScript.</p>
<p>A getter is a special object function that maps to a property. When we access the <code>count</code> property on the returned object, it runs the count function to get that property’s value.</p>
<p>And this is important because we want to return a live reference to the <code>count</code> state. Remember that when you interact with <code>count</code>, it’s just a number. And if we just returned a number, then there would be no way to update that number after we returned it.</p>
<p>Instead, we want to always get the live value that the <code>increment</code> function is updating. And that’s what the getters give us — they let us return the same underlying value that the increment function is operating on.</p>
<p>When wrapping up reactive state like this, you can return regular objects, or you can also use classes.</p>
<p>This is also a valid way to write that same counter, with “count” and “doubled” as reactive class properties.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Counter</span> <span class="token punctuation">{</span><br>  count <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  doubled <span class="token operator">=</span> <span class="token function">$derived</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token function">$effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>count <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'greater than 10'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>      <span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token function-variable function">increment</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token operator">++</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Either way, you can use these similarly: import a function or class constructor and call them, and then use the values in your script tag.</p>
<h2>are the vibes off?</h2>
<p>I talked earlier about how when planning this change, we wanted to make sure to preserve Svelte’s vibes. So now the question is — did we succeed?</p>
<p>I think only the community can really decide that. For the most part, feedback has been positive and people are excited about Svelte 5.</p>
<p>But if anything looks less Svelte-like to you, it’s probably that you now have to type “$state” out instead of it being inferred. Why do we have to do that?</p>
<p>We could have switched to runtime reactivity without changing the syntax. But we also wanted to unlock universal reactivity.</p>
<p>And if we want to declare state outside of components, we need a way to declare it explicitly. We were able to get away with implicit state in Svelte 3 and 4 because it only applied to variables at the top level of a component.</p>
<p>So in this Svelte 4 example, name and numbers are reactive state variables, but sum and value are not, because they’re not at the top level.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    <span class="token keyword">let</span> name <span class="token operator">=</span> <span class="token string">'world'</span><span class="token punctuation">;</span><br>    <span class="token keyword">let</span> numbers <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">function</span> <span class="token function">calculateSum</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        <span class="token keyword">let</span> sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> value <span class="token keyword">of</span> numbers<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>            sum <span class="token operator">+=</span> x<span class="token punctuation">;</span><br>        <span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>But now in Svelte 5, state can be anywhere — at the top level of a component, inside other functions, inside other files, or inside other functions inside other files. If we assumed every let was a state declaration like Svelte 4, it would get very confusing to figure out if a variable was truly reactive or not.</p>
<p>And it would also have a performance impact — making a value reactive isn’t expensive, but it would add up if we applied it to every variable in .svelte files.</p>
<p>It also brings a readability benefit. Code is read more often than it’s written, and now you have a clear visual signifier as to what is state and what isn’t.</p>
<p>And if you’re concerned about saving your fingers — well, that’s what editor autocomplete is for</p>
<p>Now, if the word “state” makes people nervous, there’s one other word that might frighten people… EFFECT.</p>
<p>I don’t think effect needs to be a scary word. Svelte had effects before now in the form of reactive blocks, we just didn’t call them that.</p>
<p>Side-effects are a natural concept in reactive UIs. They’re often overused — our docs encourage using derived state instead, if possible — but they’re necessary.</p>
<p>And one thing to note about Svelte’s “effect” — unlike useEffect, there’s no dependency array! If you’re familiar with Vue’s “watchEffect” or Solid’s &quot;createEffect”, it works similarly — the effect determines when to re-run based on what reactive values you access inside it.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> color <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token string">'red'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">let</span> size <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token function">$effect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> context <span class="token operator">=</span> canvas<span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token string">'2d'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token comment">// this will re-run whenever `color` or `size` change</span><br>  context<span class="token punctuation">.</span>fillStyle <span class="token operator">=</span> color<span class="token punctuation">;</span><br>  context<span class="token punctuation">.</span><span class="token function">fillRect</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> size<span class="token punctuation">,</span> size<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>As you can see in this example — by accessing the values of size and color inside the effect, Svelte will automatically re-run the effect when those values change. And if you just want to read the value in an effect without subscribing to updates, Svelte provides an “untrack” function.</p>
<p>Beyond the potentially visceral reaction to seeing words like “state” and “effect” in Svelte, the rest of what makes Svelte special is still intact.</p>
<p>For instance — you interact with the raw state values, instead of going through a setState function or an object with a value property. And to understand why that’s possible, we need to talk a little about signals…</p>
<h2>Svelte 5 and signals</h2>
<p>Svelte is finally on the “signals” train. Today it feels like every framework is either using signals or about to adopt signals, aside from React.</p>
<p>But what does “signals” actually mean? A deep dive is outside the scope of this talk. And our goal is that you should be able to use and learn Svelte 5 without even learning what a “signal” actually is.</p>
<p>But for the curious — a signal is an object that holds a value, and that can also track when the value is read. This means that when you update a signal, the signal knows all the other places that value is used, and can trigger updates in those places automatically. This lets you make very targeted, precise updates to your UI without a lot of wasted work.</p>
<p>But while signals are great for performance, interacting with them doesn’t feel very Svelte-like.</p>
<p>Part of what makes Svelte “Svelte” is this.</p>
<pre class="language-js"><code class="language-js">count <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></code></pre>
<p>It’s the fact that you can take a reactive variable, and when you want to make changes to it you don’t have to use a “set” function or set a “value” property. You can just update the value like it was a normal JavaScript variable.</p>
<p>But then how do we get signals to work? Remember, signals need to be able to track when its value is read and written to, and there’s no way to do that with a regular number in JavaScript. And this is why other frameworks have set functions and value properties that we’re trying to avoid.</p>
<p>In Vue, when you declare a state variable with a number in it, you actually get an object back, not the number. Behind the scenes Vue uses a Proxy object that intercepts reads on the value property and wires up the signal properly.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">ref</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>count<span class="token punctuation">.</span>value<span class="token operator">++</span><span class="token punctuation">;</span></code></pre>
<p>And this is why in Solid, you get back two functions that you call to read and write the value.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">createSignal</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token function">setCount</span><span class="token punctuation">(</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And there is nothing wrong with doing things this way — this is just a limitation of how JavaScript works — but it didn’t feel right for Svelte.</p>
<p>Luckily, Svelte doesn’t need to be limited by what is possible at runtime in vanilla JS — because, <a href="https://twitter.com/Rich_Harris/status/1057290365395451905">to quote Rich Harris</a> when he discovered the core of Svelte 3 — &quot;WE'RE A COMPILER, MOFOS.&quot;</p>
<p>In Svelte, we start with what we want the authoring experience to be, and use the compiler to make that possible at runtime. So in your Svelte component you write this…</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token function">$state</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>count <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span></code></pre>
<p>And Svelte compiles it to use signal methods under the hood.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> count <span class="token operator">=</span> $<span class="token punctuation">.</span><span class="token function">source</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>$<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>count<span class="token punctuation">,</span> $<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>So we get the best of both worlds — an efficient, readable authoring experience that’s easy for humans to write, that is then transformed into valid, optimized JavaScript for the browser to execute.</p>
<p>Signals, but as an implementation detail. You can interact with state values like you’re writing vanilla JS and let Svelte’s compiler handle the rest. And with Svelte 5’s universal reactivity, that’s true inside Svelte components as well as regular JS files.</p>
<h2>Svelte 5 and perf</h2>
<p>Svelte 4 was already fast, but Svelte 5 has made even more significant gains in performance. A lot of that is thanks to Dominic Gannaway, creator of Inferno.js and former React team member, who joined the Svelte team last year and has been instrumental in eking out as much performance as possible.</p>
<p>If you look at the <a href="https://krausest.github.io/js-framework-benchmark/2024/table_chrome_126.0.6478.55.html">JS Framework Benchmark</a>, which tracks how long it takes various frameworks to perform different DOM updates, Svelte 5 is doing very well, way better than Svelte 4 and neck and neck with Solid. And not only is Svelte 5 fast, but it also uses less memory and hydrates more quickly.</p>
<p>There are lots of reasons why Svelte is even faster now, many outside my expertise, I'll just mention a couple.</p>
<p>The compiler has been re-written from the ground up with performance optimization in mind, including making sure that the compiled JS code is easy for the browser engines to optimize. A lot of these may be microimprovements, but microimprovements add up.</p>
<p>And reactivity being signal-based means that updates can be more targeted and efficient than with Svelte 4’s compiled reactivity.</p>
<p>Svelte is not the absolute fastest framework out there, but it is definitely — almost blazingly? —fast. If you want to build a performant web app, Svelte is not going to require a lot of extra work from you.</p>
<h2>does Svelte scale?</h2>
<p>We’ve also made some tradeoffs in regards to bundle size.</p>
<p>In Svelte 4, a “hello world” app was only a few kilobytes, since Svelte had almost no runtime. However, because reactivity was compiled into the component, as you added more components the bundle size increased at a steeper rate than frameworks with larger runtimes. With code splitting, most apps would not hit this inflection point, but it was still something we wanted to address.</p>
<p>But now with Svelte 5, reactivity is now in the runtime, which means a slightly larger runtime, but smaller components. So a “hello world” app is slightly larger; but as you build bigger apps you’ll start to see the savings. Let’s look at an example.</p>
<p>An implementation of TodoMVC, the famous Todo app project used to demo different frameworks, is 7.2kb in Svelte 5 and 4.9kb in Svelte 4. So Svelte 4 is obviously smaller</p>
<p>But lets say you have an app that’s just three times the size of TodoMVC. At that point, the Svelte 5 app is actually smaller than Svelte 4 — 9.8KB instead of 10.1KB — because the larger runtime means that not as much needs to be compiled into each component.</p>
<p>And these savings only increase the larger your app is.</p>
<p>(For the methodology and benchmark code, see <a href="https://github.com/geoffrich/component-size-benchmark">this repo</a>).</p>
<h2>but wait, there's more!</h2>
<p>I’ve tried to talk about as much of Svelte 5 as I can, but there’s still more stuff and I only have 25 minutes! So — I’m going to fall back to a listicle.</p>
<p>These are more new features coming to Svelte 5 that I don’t have time to go into detail on. Check out <a href="https://svelte-5-preview.vercel.app/docs">the Svelte 5 docs</a>, or come find me after for more on these.</p>
<ul>
<li>Snippets</li>
<li>Event attributes</li>
<li>$derived.by</li>
<li>TypeScript in markup</li>
<li>Reactive Sets/Dates/Maps/URLs</li>
<li>$bindable</li>
<li>Deeply reactive objects and arrays</li>
<li>Fine grained reactivity</li>
<li>$inspect</li>
</ul>
<h2>but wait, there's less!</h2>
<p>But to me what’s exciting is not just all the APIs we’re adding, but the APIs we’re deprecating.</p>
<p>These are all APIs that existed in Svelte 4 that no longer need to exist in Svelte 5, either because they’ve been replaced by something simpler or because the problem they solved is no longer a problem with the new design</p>
<ul>
<li>createEventDispatcher → event attributes / callback properties</li>
<li><code>&lt;slot&gt;</code> and slot props → snippets</li>
<li><code>&lt;svelte:fragment&gt;</code> → snippets</li>
<li>$$props, $$restProps, $$slots → $props</li>
<li>beforeUpdate and afterUpdate → $effect.pre / $effect</li>
</ul>
<p>This is the benefit of rethinking some of these APIs. Any design accumulates awkward bits over time as you discover use cases that weren’t considered originally or rethink priorities. Designing from first principles means you can create something that feels cohesive instead of tacked on.</p>
<h2>incremental migration</h2>
<p>One last piece I want to touch on before closing is the migration story. I talked about a lot of changes today for how you write Svelte components.</p>
<p>Importantly, while Svelte 5 is introducing new reactivity syntax with runes, you don’t have to convert everything to runes to start using Svelte 5.</p>
<p>Instead, you can use your Svelte 4 components in Svelte 5 today, and they should work the same. Runes take effect on a per-component level — so you can have some components written using the old reactivity, and some written using the new reactivity, and they should work together.</p>
<p>Being able to incrementally migrate to the new reactivity system is important to us, since requiring massive, big-bang rewrites would make Svelte 5 very difficult for a lot of projects to adopt.</p>
<p>In addition to supporting Svelte 4-style components in Svelte 5, we’ll also be supplying automated migration tooling to convert your components to runes. Because even though the old reactivity model is supported in Svelte 5, it will eventually be removed in Svelte 6 or 7 in favor of Runes.</p>
<p>While it’s great for adoption to support both syntaxes simultaneously, it’s not great for learning and understanding. We would essentially split the ecosystem, where some components would be written in the old syntax and some in the new, and newcomers to Svelte would have to decide which one they want to learn. In our mind, the benefits of runes are clear, and you should be using them eventually — but you don’t need to adopt them all at once, and you don’t need to do it all by hand.</p>
<h2>where to next?</h2>
<p>So that’s a very brief overview of Svelte 5 - the changes, the problems they solve, and the exciting improvements coming to Svelte. We talked a lot about how Svelte 5 compares to its predecessor, but its simpler mental model means this is also a great place to start trying Svelte.</p>
<p>If you’re interested in learning more or giving it a go yourself, there are a few places you can go.</p>
<p>For now, Svelte 5 is still in preview, so the main docs on <a href="https://svelte.dev">svelte.dev</a> are still for Svelte 4. You can find the docs for Svelte 5 as well as an interactive playground at <a href="http://svelte-5-preview.vercel.app">svelte-5-preview.vercel.app</a>. Once Svelte 5 is released, it will come with an interactive tutorial that will be the best way to learn Svelte 5.</p>
<p>If you prefer to work locally, run <code>npm create svelte</code> to start a new Svelte app, which will give you the option to install the Svelte 5 preview.</p>
<p>If you want to find me, head over to my website at geoffrich.net [note: you're here], where I have my blog and links to my various socials. That’s where you can also find my slides and notes from this talk.</p>
<p>Thanks so much for having me, and enjoy the rest of the conference.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>You can’t preload SVG sprites (but I want to)</title>
    <link href="https://geoffrich.net/posts/preloading-svgs/"/>
    <updated>2024-05-16T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/preloading-svgs/</id>
    
    <content type="html"><![CDATA[
      <p><a href="https://ryantrimble.com/blog/what-the-heck-is-an-svg-sprite-sheet/">SVG sprites</a> are a great way to manage a lot of SVG icons. Create a sprite sheet with your different icons defined as symbols…</p>
<pre class="language-svg"><code class="language-svg"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2000/svg<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>defs</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>symbol</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>arrow-path<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span> <span class="token attr-name">stroke</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>currentColor<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1.5<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 24 24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">stroke-linecap</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-linejoin</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M16 9h5v0M3 20v-5m0 0h5m-5 0 3 3a8 8 0 0 0 14-4M4 10a8 8 0 0 1 14-4l3 3m0-5v5<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>symbol</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>symbol</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>clipboard<span class="token punctuation">"</span></span> <span class="token attr-name">fill</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>none<span class="token punctuation">"</span></span> <span class="token attr-name">stroke</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>currentColor<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>1.5<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 24 24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>path</span> <span class="token attr-name">stroke-linecap</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">stroke-linejoin</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>round<span class="token punctuation">"</span></span> <span class="token attr-name">d</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>M16 4a2 2 0 0 0-2-2h-3C9 2 9 3 8 4m8 0s0 0 0 0v1a1 1 0 0 1-1 0H9a1 1 0 0 1-1 0v0-1m8 0h2l2 2v14a2 2 0 0 1-3 2H7a2 2 0 0 1-2-2V6l1-2a48 48 0 0 1 2 0<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>symbol</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>defs</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre>
<p>And then you can reference those icons in your HTML with the <code>&lt;use&gt;</code> element.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>24<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#arrow-path<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre>
<p>This is great for performance, since instead of repeating the same SVG markup every time you use the icon, you define the markup one place instead and re-use it.</p>
<p>The SVG sprite sheet can either be in the same HTML document where you use it, or it can be an external asset.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- reference in the same document --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>24<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>#arrow-path<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- reference an external /sprite.svg file --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</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>24<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>24<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>use</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/sprite.svg#arrow-path<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span></code></pre>
<p>The benefit of making it an external asset is that your SVG icons can be cached independently of the page itself, decreasing page size. Unfortunately, there’s a problem: using an external sprite sheet means there’s an extra request that has to happen before the icon can be displayed, and your users might see the icons &quot;pop in&quot; as the SVG sprite sheet finishes downloading.</p>
<p>But that’s what <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload">preload links</a>, are for right?</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>preload<span class="token punctuation">"</span></span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image/svg+xml<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/sprite.svg<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>But that doesn’t work. In Chrome, you see a console warning:</p>
<blockquote>
<link rel=preload> must have a valid `as` value
</blockquote>
<p>Hm. What if we preload it as an image?</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>preload<span class="token punctuation">"</span></span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image/svg+xml<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/sprite.svg<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>No console warning when the page loads… but a few seconds later:</p>
<blockquote>
<p>The resource http://127.0.0.1:8080/sprite.svg was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate <code>as</code> value and it is preloaded intentionally.</p>
</blockquote>
<p>And if you take a look at the network panel, &quot;sprite.svg&quot; is fetched <em>twice</em>. So the browser &quot;preloaded&quot; the SVG file but wasn’t able to actually use it for our sprite references. Ugh.</p>
<p>That preload would work if we were using an SVG as an image, but not as an <code>&lt;svg&gt;</code>.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- in the head --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>preload<span class="token punctuation">"</span></span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>image/svg+xml<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/clipboard.svg<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><span class="token comment">&lt;!-- then these will be successfully preloaded --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>clipboard-bg<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>/clipboard.svg<span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>24<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>24<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.clipboard-bg</span> <span class="token punctuation">{</span><br>    <span class="token property">background</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'/clipboard.svg'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span><br>    <span class="token property">width</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br>    <span class="token property">height</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br>    <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>It turns out there’s <a href="https://issues.chromium.org/issues/40681653">a Chrome</a> (and <a href="https://github.com/whatwg/fetch/issues/1012">fetch spec</a>) issue for this behavior that has been open since 2020. For now, if you want to preload SVGs to use in <code>&lt;svg&gt;</code> elements, you’re out of luck. In fact, attempting to preload SVGs for this use case is actually harmful to performance due to the double request.</p>
<p>Workarounds for now:</p>
<ul>
<li>Accept the icons popping in.</li>
<li>Inline the sprite sheet on each page. If you don’t have a ton of icons, the cacheability tradeoff may be okay.</li>
</ul>
<p>Astro also has an interesting approach in their <a href="https://www.astroicon.dev/guides/components/#automatically-optimized-sprites">icon component</a> where the first time you render an icon it includes the <code>&lt;symbol&gt;</code>, and subsequent usages refer back to that symbol. This way icons are inlined (so no preloading to worry about) and you only include the icons you actually use.</p>
<p>To test out SVG preloading yourself, see <a href="https://github.com/geoffrich/svg-preload-demo">my demo repo</a>.</p>
<h2>A takeaway</h2>
<p>One takeaway I had from this investigation is that it’s important to verify that code changes you make actually do something. I read a tutorial that suggested preloading the SVG sprite sheet for performance, and I could’ve just assumed that it would speed up my page. However, attempting to preload was actually causing a double request and increasing the amount of resources my page loaded.</p>
<p>Similarly, you may assume that adding ARIA attributes to an element will &quot;help accessibility,&quot; but if you can’t actually check that then you may be doing more harm than good. &quot;role=button&quot; is harmful if you’re not adding the necessary keyboard interactions (probably just use a <code>&lt;button&gt;</code>), and &quot;aria-label&quot; does nothing on a div.</p>
<p>You can’t just assume things will work — you need to verify.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Reading assets on the server in SvelteKit</title>
    <link href="https://geoffrich.net/posts/sveltekit-read/"/>
    <updated>2024-01-21T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/sveltekit-read/</id>
    
    <content type="html"><![CDATA[
      <p>SvelteKit 2.4 brought a new <a href="https://kit.svelte.dev/docs/modules#$app-server-read"><code>read</code></a> helper function to read an asset from the filesystem. This is a quick post on how this function simplifies some very hacky code I wrote for an old demo.</p>
<p>Back in 2022 I wrote <a href="/posts/svelte-social-image/">an extensive post</a> on using Vercel's <a href="https://github.com/vercel/satori">Satori library</a> to create social sharing images using Svelte. I just went back and updated that post, so it should still be relevant for 2024. However, the most awkward part of that first demo was needing to write a <a href="https://github.com/geoffrich/sveltekit-satori/blob/5af7b2b96568a2c482e24aa843f14583d31e5370/vite.config.js#L14-L27">custom Vite plugin</a> to let us import a font and transform it into the raw font data that Satori needs.</p>
<p>Part of the reason we needed to do this is because there wasn't a great way to read the file from the filesystem at runtime. Sure, at dev time we can use Node's <code>fs.readFileSync</code> to read the font data. However, once we deploy our app, we don't have the same guarantees about how the filesystem is structured, and accessing the asset <a href="https://github.com/sveltejs/kit/issues/10594">gets more complicated</a>.</p>
<p>SvelteKit's new <code>read</code> helper simplifies this. If you give it the URL of an <a href="https://vitejs.dev/guide/assets#importing-asset-as-url">imported asset</a>, it will return a Response with the contents of that asset. So instead of writing a custom plugin to get the raw font data, I can do this instead:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>read<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/server'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> sourceSerifPro <span class="token keyword">from</span> <span class="token string">'$lib/fonts/SourceSerifPro-Regular.ttf'</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> fontData <span class="token operator">=</span> <span class="token function">read</span><span class="token punctuation">(</span>sourceSerifPro<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">arrayBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token comment">// then later, when calling Satori:</span><br><span class="token function">satori</span><span class="token punctuation">(</span>markup<span class="token punctuation">,</span> <span class="token punctuation">{</span><br>  <span class="token literal-property property">fonts</span><span class="token operator">:</span> <span class="token punctuation">[</span><br>    <span class="token punctuation">{</span><br>      <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Source Serif Pro'</span><span class="token punctuation">,</span><br>      <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token keyword">await</span> fontData<span class="token punctuation">,</span><br>      <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token string">'normal'</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">]</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Much cleaner! You can take a look at the <a href="https://sveltekit-satori.vercel.app">deployed demo</a> to see it in action.</p>
<p>Other quick notes:</p>
<ul>
<li>this functionality needs to be implemented by the adapter you're using. The <a href="https://github.com/sveltejs/kit/pull/11649">initial PR</a> included support for Vercel and Netlify (for serverless functions, not edge functions), as well as the regular Node adapter.</li>
<li>because this uses Vite's asset handling, <a href="https://vitejs.dev/guide/features.html#glob-import">glob imports</a> work too</li>
</ul>
<p>For another look at this feature, see Rich Harris' demo video:</p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="m4G-6dyF1MU" style="background-image: url('https://i.ytimg.com/vi/m4G-6dyF1MU/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: New SvelteKit feature: import { read } from &#39;$app/server&#39;</span>
  </button>
</lite-youtube>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte Radio Episode 70: View Transitions in SvelteKit and beyond</title>
    <link href="https://geoffrich.net/posts/svelte-radio-view-transitions/"/>
    <updated>2023-12-05T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-radio-view-transitions/</id>
    
    <content type="html"><![CDATA[
      <p>I returned to the <a href="https://www.svelteradio.com/">Svelte Radio</a> podcast a couple weeks ago to talk all about View Transitions and Svelte! We talked about:</p>
<ul>
<li>how the browser can handle page transitions now</li>
<li>using SvelteKit's <code>onNavigate</code> lifecycle hook to trigger a view transition</li>
<li>how view transitions compare to Svelte's built-in transitions</li>
</ul>
<p>You can <a href="https://www.svelteradio.com/episodes/view-transitions-in-sveltekit-and-beyond-with-geoff-rich">listen to the episode</a> on all major podcasting platforms. Here are some relevant links from the conversation:</p>
<ul>
<li>Jake Archibald's <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">view transitions explainer</a></li>
<li>the <a href="https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate">onNavigate</a> lifecycle method</li>
<li><a href="https://github.com/paoloricciuti/sveltekit-view-transition">sveltekit-view-transition</a>, a library to make work with view transitions in SvelteKit easier</li>
<li>my <a href="https://youtu.be/K95TQ-Yh7Cw?si=mrorkfV6SWBuhMy7">Svelte Summit talk</a> on view transitions in Svelte</li>
<li><a href="/tags/view%20transitions/">everything I've written</a> on view transitions</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Conditionally stream data in SvelteKit</title>
    <link href="https://geoffrich.net/posts/conditionally-stream-data/"/>
    <updated>2023-09-12T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/conditionally-stream-data/</id>
    
    <content type="html"><![CDATA[
      <p>If you return a <em>nested</em> promise from a SvelteKit load function, the result will be streamed to the browser as it resolves. This can be a great way to show the user the page as quickly as possible, and stream the slow data in as it’s available.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// +page.server.js</span><br><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> slowData <span class="token operator">=</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">nested</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">slow</span><span class="token operator">:</span> slowData<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">await</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> <span class="token string">'😴'</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token comment">// helper to simulate a delay for the given number of milliseconds</span><br><span class="token keyword">function</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>We can then show a loading state using Svelte's <a href="https://svelte.dev/docs/logic-blocks#await">await block</a>:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">await</span> data<span class="token punctuation">.</span>nested<span class="token punctuation">.</span>slow<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Loading...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span>then result<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>result<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">await</span><span class="token punctuation">}</span></span></code></pre>
<p>“Nested” here has nothing to do with the object property name, but whether the property is inside the top-level object returned from load or nested inside another object. For example, in the following object <code>panda</code> is nested but <code>tangerine</code> is not. This means that SvelteKit will wait for <code>promise2</code> to resolve before rendering the page, but will not wait for <code>promise1</code> and will stream the result in after it resolves.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">return</span> <span class="token punctuation">{</span><br>  <span class="token literal-property property">something</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">panda</span><span class="token operator">:</span> promise1<br>  <span class="token punctuation">}</span><span class="token punctuation">,</span><br>  <span class="token literal-property property">tangerine</span><span class="token operator">:</span> promise2<br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>For more, see <a href="https://kit.svelte.dev/docs/load#streaming-with-promises">the documentation</a>.</p>
<p>However, streaming data comes with its downsides.</p>
<ul>
<li>It only works when JavaScript is enabled — if you stream data, the server-rendered HTML will not have the resolved value. Because of this, SvelteKit recommends you only stream <em>non-essential</em> data so users can still get the information they need when JavaScript is not available.</li>
<li>Even if you don’t care about supporting users without JavaScript, if you server-side render your app then streaming data will cause a “flicker” of loading state on the initial page load, no matter how quickly the promise resolves.</li>
</ul>
<p>Let’s see how we can resolve those issues by <em>conditionally</em> streaming data depending on the request.</p>
<h2>Conditional streaming with <code>await</code></h2>
<p>First, let’s see how to conditionally stream a promise with SvelteKit. We saw earlier how to stream slow data, but how do you do that conditionally?</p>
<p>In our case, we just have to change one line:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> shouldStreamData <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> slowData <span class="token operator">=</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">nested</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token comment">// conditionally return here</span><br>      <span class="token literal-property property">slow</span><span class="token operator">:</span> shouldStreamData <span class="token operator">?</span> slowData <span class="token operator">:</span> <span class="token keyword">await</span> slowData<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Now, instead of always returning a promise, we return either a promise or the resolved value of that promise.</p>
<p>Importantly, we have to make sure to return the actual resolved value of the promise, <em>not</em> the resolved promise itself. This won’t work:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> shouldStreamData <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> slowData <span class="token operator">=</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>shouldStreamData<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token comment">// doesn't work!</span><br>    <span class="token keyword">await</span> slowData<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">nested</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token comment">// this promise is resolved, but Svelte will first render the unresolved state</span><br>      <span class="token literal-property property">slow</span><span class="token operator">:</span> slowData<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>This is because we’re still returning a promise to the UI, so we’ll see a flash of loading state. At least for now, Svelte’s SSR renderer is <em>synchronous</em>. Promise values can only be retrieved <em>asynchronously</em> (even if already resolved), so Svelte can’t get the resolved value immediately. For more, see <a href="https://github.com/sveltejs/svelte/issues/958">this Svelte issue</a>.</p>
<p>Instead, you should return either the promise, or the resolved value of that promise.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> toReturn <span class="token operator">=</span> shouldStreamData <span class="token operator">?</span> promise <span class="token operator">:</span> <span class="token keyword">await</span> promise<span class="token punctuation">;</span></code></pre>
<p>The great thing about this is you can still use the <code>{#await}</code> block you were using before. SvelteKit will also correctly <a href="https://svelte.dev/blog/zero-config-type-safety">generate the types</a> so that <code>data.nested.slow</code> will either be a <code>string</code> or a <code>Promise&lt;string&gt;</code>.</p>
<p>So that’s how we can conditionally stream data and choose between showing a loading state quickly and rendering a complete page. When might we want to do this?</p>
<h2>Preventing a loading flicker with <code>Promise.race</code></h2>
<p>Sometimes you don’t know if an API call is going to complete quickly or not. Maybe on a warmed-up cache it would return in 50ms, but in the worst case it could take up to a second or two. Because it can be slow, you want to stream in the data instead of waiting on it to render the page. However, if it does come back quickly, you’d rather return a rendered page with all the data present and prevent the dreaded loading state flicker.</p>
<p>We can use <code>Promise.race</code> to give our promise a few hundred milliseconds to resolve. <code>Promise.race</code> takes an array of promises, and resolves when any one of those promises resolve. We can pass our delay call and our data fetching call, and conditionally await the result depending on which one resolves first.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">TIME_TO_RESOLVE_MS</span> <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">;</span><br><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">load</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> slowData <span class="token operator">=</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> Promise<span class="token punctuation">.</span><span class="token function">race</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">delay</span><span class="token punctuation">(</span><span class="token constant">TIME_TO_RESOLVE_MS</span><span class="token punctuation">)</span><span class="token punctuation">,</span> slowData<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">nested</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">slow</span><span class="token operator">:</span> result <span class="token operator">?</span> result <span class="token operator">:</span> slowData<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token comment">// randomly delay either 50ms or 1s</span><br>  <span class="token comment">// this simulates variable API response times</span><br>  <span class="token keyword">await</span> <span class="token function">delay</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0.5</span> <span class="token operator">?</span> <span class="token number">50</span> <span class="token operator">:</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> <span class="token string">'😴'</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token keyword">function</span> <span class="token function">delay</span><span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>In this example, we race two promises: a 200ms delay and the actual data call we want to make. If the delay resolves first, then the data call is taking longer than 200ms and we should go ahead and render the page with partial data. If the data call resolves first, then we got the data under the time limit and we can render the page with complete data.</p>
<p>This flicker is mainly relevant on the initial, server-rendered page load. Subsequent client-side navigations will <a href="https://kit.svelte.dev/docs/link-options#data-sveltekit-preload-data">preload the data</a> when the link is hovered and the user might not see a loading state if the data comes back quickly enough. However, this can be a nice technique to prevent the flicker when the app is starting up.</p>
<h2>Completely loading data on initial request</h2>
<p>The <a href="https://kit.svelte.dev/docs/load#streaming-with-promises">promise streaming announcement</a> blog post highlighted an important caveat:</p>
<blockquote>
<p>One caveat: this feature needs JavaScript. Because of this, we recommend that you only stream in non-essential data so that the core of the experience is available to all users.</p>
</blockquote>
<p>Because JavaScript is needed to load the result of the streamed promises, users without JavaScript available (<a href="https://www.kryogenix.org/code/browser/everyonehasjs.html">more likely than you think</a>) will not see the fully loaded data. Instead, they will only get whatever was present when the page first rendered. This might be okay for non-essential data, like comments on a blog post, but essential data, like the post itself, should be available for all users.</p>
<p>But what if you have slow data that is also essential? Ideally you can fix the data source so that it’s no longer slow, but that’s not always possible. Do you make all users wait on that data, even though JavaScript is likely available for most of them?</p>
<p>We can instead implement a hybrid approach. On the initial page load, we could wait for all data to resolve. That way, users without JavaScript will be able to see all the content on the page, even if it loads slightly slower. If you built your app <a href="https://kit.svelte.dev/docs/form-actions#progressive-enhancement">in a progressively-enhanced way</a>, they can even perform essential actions.</p>
<p>On subsequent page loads using <a href="https://kit.svelte.dev/docs/glossary#routing">client-side navigation</a>, we can allow the slow data to be streamed in. Client-side navigation will only occur if JavaScript is available, so there are no downsides to using promise streaming. If the user doesn’t have JavaScript, each navigation will trigger a full page load, and we will again wait for all data to resolve.</p>
<p>We can switch between these two behaviors using the <code>isDataRequest</code> property on the <a href="https://kit.svelte.dev/docs/types#public-types-requestevent">RequestEvent</a> passed to the load function. When this property is false, the request is for the HTML for the initial page load, and we should wait for all data to resolve. If the property is true, then the request is from SvelteKit’s client-side router and we can stream the data in.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">load</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>isDataRequest<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> slowData <span class="token operator">=</span> <span class="token function">getSlowData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">nested</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">slow</span><span class="token operator">:</span> isDataRequest <span class="token operator">?</span> slowData <span class="token operator">:</span> <span class="token keyword">await</span> slowData<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>However, note that this will impact your <a href="https://web.dev/ttfb/">Time to First Byte</a> for all users, since the browser will not receive any HTML until the data has fully loaded. You should carefully consider the tradeoffs between response time and serving users without JavaScript, which will depend on your app’s userbase:</p>
<ol>
<li>If all your data is fast, don’t worry about promise streaming</li>
<li>If some of your data is slow, but it’s non-essential, use promise streaming for that data</li>
<li>If some of your data is slow and essential, and it’s important to serve users without JavaScript, either speed up the slow data at the source or use <code>isDataRequest</code> to conditionally await data</li>
</ol>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Unlocking view transitions in SvelteKit 1.24</title>
    <link href="https://svelte.dev/blog/view-transitions"/>
    <updated>2023-08-31T00:00:00Z</updated>
    <id>https://svelte.dev/blog/view-transitions</id>
    
    <summary>Posted on Svelte: </summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>90 minute SvelteKit Crash Course with This Dot Labs</title>
    <link href="https://geoffrich.net/posts/jsdrops/"/>
    <updated>2023-08-24T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/jsdrops/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="MaF8kRbHbi0" style="background-image: url('https://i.ytimg.com/vi/MaF8kRbHbi0/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Mastering SvelteKit with Geoff Rich | JS Drops</span>
  </button>
</lite-youtube>
<p>I recorded a 90 minute SvelteKit crash course with This Dot Labs where we build a music library app (which I named &quot;Sveltunes&quot;). My goal was to give a overview of <em>everything</em> that makes me excited about SvelteKit, so we cover a lot. Topics include:</p>
<ul>
<li>directory-based routing, layouts, parameterized routes, and nested routing</li>
<li>loading data with the <code>load</code> function (and how this is more efficient than loading data with <code>onMount</code>)</li>
<li>preloading the data for the next page for snappy navigations</li>
<li>streaming slow data from <code>load</code> functions by returning nested promises</li>
<li>how SvelteKit lets you mix-and-match different rendering options (SSR, CSR, prerendering)</li>
<li>submitting and validating data with form actions</li>
<li>using the <code>enhance</code> helper to progressively enhance forms</li>
<li>implementing optimistic UI</li>
</ul>
<p>There's a lot we didn't get to, but hopefully this provides a good overview! The code for <a href="https://github.com/geoffrich/sveltunes">Sveltunes</a> can be found on GitHub.</p>
<p>If you enjoyed this video, I recommend checking out the slides for the talk I gave at <a href="/posts/thatconf-2023/">THAT Conference</a>, where I build on the data loading and progressive enhancement concepts to build a nested &quot;Favorite Albums&quot; UI.</p>
<p>In fact, both of my in-person talks last month (the other was at <a href="/posts/seattlejs-2023/">SeattleJS</a>) used the Sveltunes demo, and that was intentional — better to build one demo you can reuse instead of three!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>THAT Conference WI 2023: Building Efficient, Resilient Web Apps With SvelteKit</title>
    <link href="https://geoffrich.net/posts/thatconf-2023/"/>
    <updated>2023-07-26T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/thatconf-2023/</id>
    
    <content type="html"><![CDATA[
      <p>In just a few hours I'm giving a <a href="https://that.us/activities/QDRKq2sPE4yXlecx182V">talk on SvelteKit</a> at <a href="https://that.us/events/wi/2023/">THAT Conference WI</a>. Here's the talk title and abstract:</p>
<blockquote>
<p><strong>Building Efficient, Resilient Web Apps With SvelteKit</strong></p>
<p>Building web apps with all the modern best practices can be complicated. Not only do you want your app to be <em>efficient</em> and load data quickly and intelligently, you also want it to be <em>resilient</em> and stay upright &gt; regardless of your users’ device or network.</p>
<p>Enter SvelteKit, a modern web framework built on top of the best-in-class Svelte JS component framework. Not only does it help you move fast with a minimal-boilerplate and <em>fun</em> developer experience, it also gives you the tools to provide an efficient, resilient user experience.</p>
<p>This talk will move beyond the basics and show how to use SvelteKit’s powerful toolkit to handle more advanced scenarios:</p>
<ul>
<li>preloading and streaming data for lightning-fast navigations</li>
<li>crafting a minimum viable experience so your app still functions when your JavaScript fails</li>
<li>sharing data between multiple routes by loading data in the layout</li>
<li>avoiding “data waterfalls” that make your users wait for no good reason</li>
<li>progressively enhanced forms</li>
<li>customizing SvelteKit’s enhanced forms to implement optimistic UI</li>
<li>using advanced Svelte store patterns to efficiently update data instead of over-fetching</li>
</ul>
<p>We’ll use a music collection demo app to showcase these ideas, so that they’re tied to concrete examples instead of being purely theoretical.</p>
</blockquote>
<p>The talk will not be recorded, but I've linked my slides and other resources below. I might transcribe a written version of this talk at a later date.</p>
<p>My slides were created using <a href="https://sli.dev/">Slidev</a>. You can find the source on <a href="https://github.com/geoffrich/thatconf-slides-2023">my GitHub</a> and the <a href="https://thatconf-slides-2023.vercel.app">live version</a> deployed to Vercel.</p>
<p>As part of the talk, I briefly showed a music library demo app (a.k.a. &quot;Sveltunes&quot;). The code for that app is <a href="https://github.com/geoffrich/sveltunes">also on GitHub</a>, though it's not currently deployed anywhere because auth is mocked out and the &quot;DB&quot; is just an in-memory object.</p>
<p>If you want to learn more about Svelte and SvelteKit, I recommend the following resources:</p>
<ul>
<li><a href="https://learn.svelte.dev">the interactive tutorial</a> (start here!)</li>
<li>the <a href="https://svelte.dev/">Svelte</a> and <a href="https://kit.svelte.dev/">SvelteKit</a> doc sites</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>SeattleJS July 2023: An introduction to SvelteKit</title>
    <link href="https://geoffrich.net/posts/seattlejs-2023/"/>
    <updated>2023-07-12T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/seattlejs-2023/</id>
    
    <content type="html"><![CDATA[
      <p>Tonight I'll be giving an <a href="https://seattlejs.com/talks/geoff-rich-july-2023">introductory talk</a> on SvelteKit at <a href="https://seattlejs.com/">SeattleJS</a>. Here's the talk title and abstract:</p>
<blockquote>
<p><strong>Web development, streamlined: an introduction to SvelteKit</strong></p>
<p>SvelteKit is an exciting new web framework that recently launched version 1.0. It combines the well-loved Svelte JavaScript framework with everything you need to build a modern web app: routing, type-safe data loading, progressively-enhanced forms, a speedy Vite-powered dev experience, and more. In this talk, I’ll give a crash course on SvelteKit and how you can use it to build fast, resilient web apps of all shapes and sizes.</p>
</blockquote>
<p>The talk will not be recorded, but I've linked my slides and other resources below. I might transcribe a written version of this talk at a later date.</p>
<p>My slides were created using <a href="https://sli.dev/">Slidev</a>. You can find the source on <a href="https://github.com/geoffrich/seattlejs-slides-2023">my GitHub</a> and the <a href="https://seattlejs-slides-2023.vercel.app">live version</a> deployed to Vercel.</p>
<p>As part of the talk, I briefly showed a music library demo app (a.k.a. &quot;Sveltunes&quot;). The code for that app is <a href="https://github.com/geoffrich/sveltunes">also on GitHub</a>, though it's not currently deployed anywhere because auth is mocked out and the &quot;DB&quot; is just an in-memory object.</p>
<p>If you want to learn more about Svelte and SvelteKit, I recommend the following resources:</p>
<ul>
<li><a href="https://learn.svelte.dev">the interactive tutorial</a> (start here!)</li>
<li>the <a href="https://svelte.dev/">Svelte</a> and <a href="https://kit.svelte.dev/">SvelteKit</a> doc sites</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Talking the release of Svelte 4 with PodRocket and This Dot Labs</title>
    <link href="https://geoffrich.net/posts/svelte-4/"/>
    <updated>2023-07-03T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-4/</id>
    
    <content type="html"><![CDATA[
      <p>Svelte 4 is here, and I guested on two shows talking about it!</p>
<p>Svelte 4 is mostly a behind-the-scenes update, and breaking changes should be minimal for most users. However, it does have some exciting improvements around custom element generation, IDE autocomplete, and hydration performance. See the <a href="https://svelte.dev/blog/svelte-4">full announcement</a> and <a href="https://svelte.dev/docs/v4-migration-guide">migration guide</a> for more.</p>
<p>First, PodRocket had me on for <a href="https://podrocket.logrocket.com/svelte-4">an episode</a> where I discussed what's new in Svelte 4 as well as what's changed on the revamped <a href="https://svelte.dev">Svelte documentation site</a>. This was a great chat with an impressive turnaround - it was edited and posted 24 hours after recording!</p>
<p>Then last week This Dot Labs (who previously hosted a <a href="/posts/state-of-svelte/">State of Svelte</a> event) had me and a couple other folks from the Svelte team (Ben McCann, Simon Holthausen, and Puru Vijay) on to discuss the release as part of a Svelte 4 Launch Party stream.</p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="-9gy_leMmcQ" style="background-image: url('https://i.ytimg.com/vi/-9gy_leMmcQ/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Svelte 4 Launch Party</span>
  </button>
</lite-youtube>
<p>More Svelte 4 resources:</p>
<ul>
<li><a href="https://svelte.dev/blog/svelte-4">full announcement</a></li>
<li><a href="https://svelte.dev/docs/v4-migration-guide">migration guide</a></li>
<li><a href="https://podrocket.logrocket.com/future-of-svelte">another Svelte 4 PodRocket episode</a> with Svelte maintainer Simon Holthausen (a.k.a. dummdidumm)</li>
<li>a <a href="https://www.youtube.com/watch?v=72TIVhRtyWE">live Svelte Radio episode</a> featuring Puru and Simon</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>A passing test is not always enough</title>
    <link href="https://medium.com/ordergroove-engineering/a-passing-test-is-not-always-enough-ae57c8f59caa"/>
    <updated>2023-06-01T00:00:00Z</updated>
    <id>https://medium.com/ordergroove-engineering/a-passing-test-is-not-always-enough-ae57c8f59caa</id>
    
    <summary>Posted on Ordergroove Engineering: </summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte Summit 2023: Svelte and View Transitions</title>
    <link href="https://geoffrich.net/posts/svelte-summit-2023/"/>
    <updated>2023-05-06T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-summit-2023/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="K95TQ-Yh7Cw" style="background-image: url('https://i.ytimg.com/vi/K95TQ-Yh7Cw/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Svelte Summit 2023</span>
  </button>
</lite-youtube>
<p>I spoke at the Spring 2023 <a href="https://www.sveltesummit.com/">Svelte Summit</a> on Svelte and view transitions. The View Transitions API is an exciting new browser API that streamlines the process of animating between two page states. While the headline use case is page transitions, it can also be used for all sorts of animation in your Svelte app. In this talk, I show how you can replace Svelte’s built-in flip and crossfade animations with view transitions, as well as the pros of cons of each approach.</p>
<p>Following is a transcript of the talk, and the entire event is available to stream on <a href="https://www.youtube.com/live/0bog8-Ay7CU">YouTube</a>. My demo code is available <a href="https://github.com/geoffrich/svelte-summit-view-transitions">on GitHub</a>, or you can visit the <a href="https://svelte-summit-view-transitions.vercel.app/">demo site</a> to see it in action. Note that the view transitions part of the demo will only work in Chrome at time of writing.</p>
<p>If you’re coming here after watching the talk, you can find a list of resources and further reading at the end of the page.</p>
<hr>
<p>Hello, I’m Geoff, a Svelte maintainer and Senior Software Engineer at Ordergroove.</p>
<p>Svelte transitions and animations — everybody loves them. In fact, they’re usually one of the top three reasons people fall in love with Svelte, right next to “simple state management” and “feeling superior about not having a virtual DOM.”</p>
<p>There are <a href="https://svelte.dev/docs#run-time-svelte-transition">7 built-in Svelte transitions</a> and <a href="https://svelte.dev/docs#run-time-svelte-animate">1 built-in animation function</a>, but today, we’re going to focus on two: <code>flip</code> and <code>crossfade</code>. We’ll talk about why we need them in the first place and how new browser capabilities might provide an alternative. Maybe.</p>
<h2>flip and crossfade</h2>
<p>Let’s start with <code>animate:flip</code>. This is a directive that you can use inside an <code>#each</code> block to make reordering the elements in that block smoother.</p>
<p>For example, look at this row of playing cards. Clicking the “Shuffle” button reorders them, but they abruptly jump to their new positions — it’s hard to understand what playing card moved where.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>shuffle<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span>shuffle<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Shuffle<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">cards </span><span class="token keyword">as</span> <span class="token language-javascript">card </span><span class="token language-javascript"><span class="token punctuation">(</span>card<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>		<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><br>		<span class="token attr-name"><span class="token namespace">style:</span>background-image="url(</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token function">getSpriteUrl</span><span class="token punctuation">(</span>card<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token attr-name">)"</span><br>		<span class="token attr-name">data-card=</span><span class="token language-javascript"><span class="token punctuation">{</span>card<span class="token punctuation">}</span></span><br>	<span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>But! If I add <code>animate:flip</code> to the individual DOM elements, they instead animate to their new position. Much nicer! It’s worth noting you do need to provide a <code>key</code> in your each loop — otherwise Svelte can’t tell which card moved where. See the Svelte tutorial on <a href="https://learn.svelte.dev/tutorial/keyed-each-blocks">keyed each blocks</a> for more.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">selected </span><span class="token keyword">as</span> <span class="token language-javascript">card </span><span class="token language-javascript"><span class="token punctuation">(</span>card<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>		<span class="token attr-name"><span class="token namespace">animate:</span>flip=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span><br>			<span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token number">400</span><br>		<span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>		<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><br>		<span class="token attr-name"><span class="token namespace">style:</span>background-image="url(</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token function">getSpriteUrl</span><span class="token punctuation">(</span>card<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token attr-name">)"</span><br>		<span class="token attr-name">data-card=</span><span class="token language-javascript"><span class="token punctuation">{</span>card<span class="token punctuation">}</span></span><br>	<span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>select<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">deselect</span><span class="token punctuation">(</span>card<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Swap<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>This is something you could implement in JavaScript, but you have to grab the DOM elements yourself and it gets a bit math-y, so it’s nice that Svelte makes it as simple as adding a single attribute.</p>
<p>But Svelte’s flip only works in individual each blocks. In this demo, you can also click a card to select it and move it to a separate row. If I want that transition to be animated, I need to reach for a different tool: the <code>crossfade</code> transition.</p>
<p>First we call <code>crossfade</code> to get the pair of transitions and then apply them to the elements in each row. We pass a unique key so Svelte knows that when we remove an element from the first block, it’s the same as the element we’re adding to the second block, and should be animated to its new position. In this case, the key is the card’s suit and rank, for example “10 hearts”.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> flip <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/animate'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> crossfade <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> quintOut <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/easing'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">const</span> <span class="token punctuation">[</span>send<span class="token punctuation">,</span> receive<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">crossfade</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br>		<span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token number">500</span><span class="token punctuation">,</span><br>		<span class="token literal-property property">easing</span><span class="token operator">:</span> quintOut<br>	<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>	<span class="token comment">// truncated for readability</span><br>	<span class="token keyword">let</span> cards<span class="token punctuation">,</span> selected<span class="token punctuation">;</span><br>	<span class="token keyword">function</span> <span class="token function">select</span><span class="token punctuation">(</span><span class="token parameter">card</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><br>	<span class="token keyword">function</span> <span class="token function">deselect</span><span class="token punctuation">(</span><span class="token parameter">card</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cards<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">cards </span><span class="token keyword">as</span> <span class="token language-javascript">card </span><span class="token language-javascript"><span class="token punctuation">(</span>card<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>			<span class="token attr-name"><span class="token namespace">animate:</span>flip=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span><br>				<span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token number">400</span><br>			<span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>			<span class="token attr-name"><span class="token namespace">in:</span>send=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">key</span><span class="token operator">:</span> card <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>			<span class="token attr-name"><span class="token namespace">out:</span>receive=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">key</span><span class="token operator">:</span> card <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>		<span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>select<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">select</span><span class="token punctuation">(</span>card<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Swap<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cards<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">selected </span><span class="token keyword">as</span> <span class="token language-javascript">card </span><span class="token language-javascript"><span class="token punctuation">(</span>card<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>			<span class="token attr-name"><span class="token namespace">animate:</span>flip=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span><br>				<span class="token literal-property property">duration</span><span class="token operator">:</span> <span class="token number">400</span><br>			<span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>			<span class="token attr-name"><span class="token namespace">in:</span>send=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">key</span><span class="token operator">:</span> card <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>			<span class="token attr-name"><span class="token namespace">out:</span>receive=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">key</span><span class="token operator">:</span> card <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>		<span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>select<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">deselect</span><span class="token punctuation">(</span>card<span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Swap<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Once we apply the transition, swapping the card will animate it to its new position.</p>
<p>If you haven’t seen these before, go take a look at the <a href="https://learn.svelte.dev/tutorial/animate">official Svelte tutorial</a> — they’re pretty fundamental bits of Svelte API, so I’m not going to dwell on the particulars much longer.</p>
<p>So Svelte’s FLIP and crossfade directives were <a href="https://github.com/sveltejs/svelte/pull/2247">introduced in 2019</a>. Are there more options for animating UI states on the web in 2023?</p>
<p>Well, yes. Let’s talk about the View Transitions API.</p>
<h2>The View Transitions API</h2>
<p>The View Transitions API has been developed in Chrome <a href="https://developer.chrome.com/blog/spa-view-transitions-land/">for a while now</a>. You may have heard about it under its previous name, “Shared Element Transitions”. According to the <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">API explainer</a>, it “makes it easy to change the DOM in a single step, while creating an animated transition between the two states.”</p>
<p>Up to this point in browser history, we had the tools to animate individual elements, but smoothly transitioning elements between two UI states was still a hard problem, and people often reached for libraries to solve it.</p>
<p>So, this API introduces a new document method: <code>startViewTransition</code>. When you call it, it will start a view transition and capture the current state of the page. Inside a callback passed to the function, you update the DOM somehow. Once you’re finished, it will capture the new state of the page, and then smoothly transition between the old and new states.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">performTransition</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">await</span> <span class="token function">updateTheDomSomehow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>By default, it will fade the old state out while fading the new state in, but you can customize the transition in CSS just like you would any CSS animation. In addition, this API also introduces a new <code>view-transition-name</code> CSS property — you’ll see how we use this in a bit.</p>
<p>I’m barely scratching the surface here, so check out the <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">official explainer</a> for a much more detailed rundown.</p>
<h2>Refactoring our code to use view transitions</h2>
<p>Let’s look back at the demo we were working on before. How can we refactor this code to use <code>startViewTransition</code> instead of Svelte’s <code>animate:flip</code> and <code>crossfade</code> ? I’ve reverted back to before we added those functions, so we have a blank slate.</p>
<p>Let’s look at the shuffle functionality. To have shuffling trigger a view transition, we can wrap the actual state updates <em>inside of</em> <code>startViewTransition</code>. The browser then needs to know when the DOM has finished updating. In Svelte, we can do that by awaiting the <code>tick</code> function.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">shuffle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token punctuation">[</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">shuffleDecks</span><span class="token punctuation">(</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">await</span> <span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>So with that, every time we shuffle, we have a transition happening - a fade. But that’s not quite what we want, is it?</p>
<p>This is because the browser only sees the old and new states — it doesn’t know whether a card moved somewhere or was replaced. Just like Svelte needed a key, so does the browser, which we can do by setting a <code>view-transition-name</code>.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.card</span> <span class="token punctuation">{</span><br>  <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--name<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Since this tag needs to be unique, I like to use a CSS custom property to inject the dynamic part of the transition tag. We can set the prop in a style directive, and then use it in our CSS. Like before, I can use the name of the card, which is unique for this demo.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">selected </span><span class="token keyword">as</span> <span class="token language-javascript">card </span><span class="token language-javascript"><span class="token punctuation">(</span>card<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>		<span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><br>		<span class="token attr-name"><span class="token namespace">style:</span>--name="card-</span><span class="token language-javascript"><span class="token punctuation">{</span>card<span class="token punctuation">}</span></span><span class="token attr-name">"</span><br>	<span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>And with that, the cards animate during shuffle.</p>
<p>I want to pause for a moment to call out just how cool this is. We have elements dynamically moving to their new positions, and there’s no JavaScript code doing calculations to make this happen. We didn’t have to write it and neither did Svelte. The browser is figuring it out for us.</p>
<p>So, should you go out and yank all the <code>animate:flip</code> and <code>transition:crossfade</code> from your codebase in favor of this new browser API? Well, not necessarily. Each version has their pros and cons — let’s compare them.</p>
<h2>Comparing the two approaches</h2>
<p>Let’s start with the pros of the view transition version:</p>
<ul>
<li>First, as I mentioned, the browser does all the heavy lifting</li>
<li>You’re also able to use CSS to customize the animation, which is especially useful if you want to use media queries to target different device sizes or reduced motion preferences</li>
<li>And because it’s CSS animation, you can easily debug the animations <a href="https://developer.chrome.com/docs/web-platform/view-transitions/#debugging-transitions">using Chrome’s animation devtools</a></li>
<li>Also, there are no Svelte templating constraints — with animate:flip, you had to be in an each block. But the View Transitions API doesn’t care about that.</li>
<li>This one probably isn’t significant enough to matter, but we do ship slightly less client-side JavaScript since we don’t need to include Svelte’s flip or crossfade code</li>
<li>And because it’s a browser API, the knowledge is portable — you can’t use Svelte animations and transitions in other frameworks, but you can use view transitions.</li>
</ul>
<p>On the other hand, it also comes with some cons.</p>
<p>It only works in Chrome, at least for now. While animation is a good progressive enhancement case — if this is broken, then your app should still work — losing this animation completely in non-Chrome browsers may be a dealbreaker. Hopefully, this won’t always be a con as the API is implemented by more browsers. Until then, make sure to check the API is supported before using it.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">shuffle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token punctuation">[</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">shuffleDecks</span><span class="token punctuation">(</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">return</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token punctuation">[</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">shuffleDecks</span><span class="token punctuation">(</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">await</span> <span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>And while we can use CSS to customize the animation, targeting the animation for multiple elements can get wordy. If we wanted to change the animation duration for all of the cards, we would need to write a selector for every transition tag we use.</p>
<pre class="language-css"><code class="language-css"><span class="token punctuation">:</span><span class="token property">root</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">view-transition-group</span><span class="token punctuation">(</span>card-A♠<span class="token punctuation">)</span><span class="token punctuation">,</span><br><span class="token punctuation">:</span><span class="token property">root</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">view-transition-group</span><span class="token punctuation">(</span>card-2♠<span class="token punctuation">)</span><span class="token punctuation">,</span><br><span class="token punctuation">:</span><span class="token property">root</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">view-transition-group</span><span class="token punctuation">(</span>card-3♠<span class="token punctuation">)</span><span class="token punctuation">,</span><br><span class="token punctuation">:</span><span class="token property">root</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">view-transition-group</span><span class="token punctuation">(</span>card-4♠<span class="token punctuation">)</span><span class="token punctuation">,</span><br><span class="token punctuation">:</span><span class="token property">root</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token function">view-transition-group</span><span class="token punctuation">(</span>card-5♠<span class="token punctuation">)</span><br><span class="token comment">/* and so on */</span> <span class="token punctuation">{</span><br>  <span class="token property">animation-duration</span><span class="token punctuation">:</span> 1s<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>There’s <a href="https://github.com/w3c/csswg-drafts/issues/8319">a proposal to solve this problem</a> by creating “classes” of transition groups, but that isn’t implemented right now.</p>
<p>Finally, we have to wrap every function that triggers a view transition in <code>document.startViewTransition</code>. So, If we want “swapping” a card to transition too, we also need to wrap it.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">select</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">card</span><span class="token operator">:</span> Card</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token punctuation">[</span>cards<span class="token punctuation">,</span> selected<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">moveCard</span><span class="token punctuation">(</span>card<span class="token punctuation">,</span> cards<span class="token punctuation">,</span> selected<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">await</span> <span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Svelte transitions are more declarative in that you can add them to the element directly, and any state affecting that element will trigger the animation.</p>
<p>So while view transitions are great, they’re not a drop-in replacement for the Svelte transitions you’re already familiar with. And they’re not meant to be! They solve similar problems in different ways, with different tradeoffs.</p>
<p>We didn’t even talk about animated page transitions — arguably <em>the</em> headline feature of the View Transitions API, and something that is tricky to do with Svelte’s transitions today. Some of the cons I mentioned become much less relevant with the page transition use case — for example, for SvelteKit page transitions, you don’t have to wrap every link that should trigger a transition — you only have to wrap a single navigation lifecycle hook.</p>
<p>(Note: at time of recording, there is an <a href="https://github.com/sveltejs/kit/pull/9605">open SvelteKit PR</a> that will make the view transitions integration much smoother by adding an <code>onNavigate</code> lifecycle hook.)</p>
<h2>Wrapping up</h2>
<p>As much as we all love Svelte, it’s important to also know about the web platform’s APIs. There may come a point where Svelte is dead and gone, but the web platform is extremely backwards-compatible, and browser APIs and fundamentals are here to stay. There’s a good chance that view transitions will become an essential tool in your toolbelt for years to come, especially as they gain wider cross-browser adoption.</p>
<p>If you want to read more about Svelte and view transitions, head to my blog (Ed: you’re already here!), where I’ll have a written version of this talk as well as links to some further reading. I also recommend checking out <a href="/posts/page-transitions-1/">my post</a> on creating animated page transitions in SvelteKit using the View Transitions API.</p>
<p>Thanks for your time, and enjoy the rest of the talks.</p>
<h2>Further reading</h2>
<p>I barely scratched the surface with what's possible with view transitions, so check out these links to learn more:</p>
<ul>
<li>the official <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">view transitions explainer</a>. I frequently come back to this, it is very in-depth and covers a lot of edge cases.</li>
<li>my blog: <a href="/posts/page-transitions-1/">SvelteKit page transitions</a> with the view transitions API</li>
<li>my demos (viewable in Chrome only): <a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/fruits">fruit list</a>, <a href="https://http-203-svelte.vercel.app/">Svelte Summit</a>, and <a href="https://sw-demo-svelte-git-shared-element-transitions-geoffrich.vercel.app/">Star Wars</a>. See my <a href="/posts/view-transition-experiments/">view transition experiments</a> write-up for some explanation and links to the source</li>
<li>and while it's not up-to-date with the breaking changes in the latest version of the API, I really enjoyed Maxi Ferreira's experiments with <a href="https://www.maxiferreira.com/blog/astro-page-transitions/">view transitions and Astro</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>State of Svelte Livestream</title>
    <link href="https://geoffrich.net/posts/state-of-svelte/"/>
    <updated>2023-04-11T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/state-of-svelte/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="hXMrdpb6fpw" style="background-image: url('https://i.ytimg.com/vi/hXMrdpb6fpw/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: State of Svelte</span>
  </button>
</lite-youtube>
<p>This Dot Labs hosted a &quot;State of Svelte&quot; livestream today with several folks from the Svelte team and community, including myself. If you’re curious about what’s happening with Svelte lately and where it’s going next, it's well worth a watch! Some of the topics include:</p>
<ul>
<li>how SvelteKit compares to the other server experiments in the ecosystem (Remix, Solid Start, React Server Components)</li>
<li>the plan for Svelte 4 and a Svelte 5 wishlist</li>
<li>testing your Svelte and SvelteKit apps</li>
<li>switching the Svelte internals to JSDoc from TypeScript</li>
<li>what makes the Svelte community special</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Refresh stale data in a SvelteKit app with QStash</title>
    <link href="https://upstash.com/blog/sveltekit-qstash"/>
    <updated>2023-03-13T00:00:00Z</updated>
    <id>https://upstash.com/blog/sveltekit-qstash</id>
    
    <summary>Posted on Upstash: </summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>View Transition Experiments with Svelte</title>
    <link href="https://geoffrich.net/posts/view-transition-experiments/"/>
    <updated>2023-02-23T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/view-transition-experiments/</id>
    
    <content type="html"><![CDATA[
      <p>The <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">View Transitions API</a> (f.k.a. Shared Element Transitions) landed unflagged in <a href="https://developer.chrome.com/blog/chrome-111-beta/#view-transitions-api">Chrome 111 Beta</a>, so I figured it was time to return to some of my demos using the API in Svelte and SvelteKit!</p>
<p>If you’re not familiar with the API, the explainer linked above is well worth the read. tl;dr, it’s a browser API that “makes it easy to change the DOM in a single step, while creating an animated transition between the two states.”</p>
<h2>Replacing Svelte’s FLIP and crossfade transitions</h2>
<p>Last year I created this <a href="https://sw-demo-svelte.vercel.app/">Star Wars demo</a> to show off Svelte’s animation and transition capabilities. As an experiment, I ripped out all the Svelte animation code and replaced it with the View Transition API, and it worked quite well.</p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="OKluqWvJIP0" style="background-image: url('https://i.ytimg.com/vi/OKluqWvJIP0/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Star Wars View Transition Demo</span>
  </button>
</lite-youtube>
<p>Here’s <a href="https://sw-demo-svelte-git-shared-element-transitions-geoffrich.vercel.app/">the experimental branch</a> deployed to Vercel — it will only work if you use Chrome Canary or the latest Beta.</p>
<p>The implementation is slightly more complicated than the previous version using <a href="https://svelte.dev/docs#run-time-svelte-animate-flip">animate:flip</a> and a <a href="https://svelte.dev/docs#run-time-svelte-transition-crossfade">crossfade transition</a>. Essentially, every state update is wrapped in a helper function that starts a view transition and waits for the state update to complete using <a href="https://svelte.dev/docs#run-time-svelte-tick">tick</a>.</p>
<p>For example, the “select movie” button looks something like this (note the wrapping <code>pageTransition</code> call):</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>IconButton</span><br>  <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">pageTransition</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">select</span><span class="token punctuation">(</span>movie<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><br>  <span class="token attr-name">label</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>select<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>@html plus<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>IconButton</span><br><span class="token punctuation">></span></span></code></pre>
<p>Because multiple state updates can happen when a single piece of state is updated thanks to the power of reactive statements, the helper <code>pageTransition</code> function will only start one transition, but run all the different queued state updates before completing the transition.</p>
<p>The helper function uses <a href="https://developer.chrome.com/docs/web-platform/view-transitions/#not-a-polyfill">a bit of code</a> from Jake Archibald to gracefully handle the API not being available (in which case, no animation will play).</p>
<p>The full <code>pageTransition</code> function looks like this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>tick<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">let</span> cbs <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br><span class="token keyword">let</span> inProgress <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br><br><span class="token keyword">function</span> <span class="token function">clearCallbacks</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">while</span> <span class="token punctuation">(</span>cbs<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> cb <span class="token operator">=</span> cbs<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><br><br><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">pageTransition</span><span class="token punctuation">(</span><span class="token parameter">fn<span class="token punctuation">,</span> shouldTransition <span class="token operator">=</span> <span class="token boolean">true</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token comment">// allows for easily toggling off the transition for certain state changes</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shouldTransition<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">return</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br>  cbs<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>inProgress<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br>  inProgress <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> t <span class="token operator">=</span> <span class="token function">transitionHelper</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br>    <span class="token function-variable function">updateDOM</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      <span class="token function">clearCallbacks</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>      <span class="token keyword">await</span> <span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>      <span class="token function">clearCallbacks</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// some callbacks may be queued in the middle of the transition, resolve those too</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  t<span class="token punctuation">.</span>finished<span class="token punctuation">.</span><span class="token function">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">clearCallbacks</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    inProgress <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token comment">// copied from Jake Archibald's explainer</span><br><span class="token keyword">function</span> <span class="token function">transitionHelper</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>skipTransition <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">,</span> classNames <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> updateDOM<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>skipTransition <span class="token operator">||</span> <span class="token operator">!</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> updateCallbackDone <span class="token operator">=</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token function">updateDOM</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 punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">return</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">ready</span><span class="token operator">:</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'View transitions unsupported'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br>      updateCallbackDone<span class="token punctuation">,</span><br>      <span class="token literal-property property">finished</span><span class="token operator">:</span> updateCallbackDone<span class="token punctuation">,</span><br>      <span class="token function-variable function">skipTransition</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  document<span class="token punctuation">.</span>documentElement<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">...</span>classNames<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> transition <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span>updateDOM<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  transition<span class="token punctuation">.</span>finished<span class="token punctuation">.</span><span class="token function">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span><br>    document<span class="token punctuation">.</span>documentElement<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token operator">...</span>classNames<span class="token punctuation">)</span><br>  <span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> transition<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>If you want to take a closer look at the implementation, see the <a href="https://github.com/geoffrich/star-wars-demo-svelte/tree/shared-element-transitions">experimental branch</a> on the repo.</p>
<h2>SvelteKit page transitions</h2>
<p>When I was experimenting with this API last year, I put together two SvelteKit page transition demos that I presented at the <a href="/posts/svelte-london-2022/">Svelte London</a> meetup. I only blogged one of them, a simple fruit list demo (which I over-confidently titled “Part 1”). The other, a SvelteKit site displaying various Svelte Summit talks that I based on <a href="https://http203-playlist.netlify.app/">Jake Archibald’s demo</a>, was mostly ready but never fully written up.</p>
<p>With the View Transitions API landing in Chrome Beta, I took the time to update these demos to the latest API, since there had been several breaking changes while the API was in an experimental state.</p>
<p>I’m pretty happy with how they both turned out — take a look (again, Chrome Canary or Beta only).</p>
<p><a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/fruits">Fruit demo</a> <a href="https://github.com/geoffrich/sveltekit-view-transitions">(source)</a></p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="kYpPuJWsOKU" style="background-image: url('https://i.ytimg.com/vi/kYpPuJWsOKU/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Fruit List View Transition Demo</span>
  </button>
</lite-youtube>
<p><a href="https://http-203-svelte.vercel.app/">Svelte Summit demo</a> <a href="https://github.com/geoffrich/http-203-svelte">(source)</a></p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="-GIO554D-6E" style="background-image: url('https://i.ytimg.com/vi/-GIO554D-6E/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Svelte Summit View Transition Demo</span>
  </button>
</lite-youtube>
<p>For the full details of how the fruit list demo works, check out my <a href="/posts/page-transitions-1/">updated post</a>. The high level overview is that we set up some code in a <a href="https://kit.svelte.dev/docs/modules#$app-navigation-beforenavigate">beforeNavigate</a> callback that starts the transition and resolves it once the navigation completes. We tell the browser which elements should transition together when changing pages by giving them the same <a href="https://developer.chrome.com/docs/web-platform/view-transitions/#transitioning-multiple-elements">view transition name</a>.</p>
<p>The Svelte Summit demo used a similar approach, but had more complex transitions and was a bit trickier (which is why I put off doing a full write-up!) I ran into a few issues where the transitions weren’t happening due to race conditions — the new state was being shown before the transition had a chance to start, so it couldn’t capture the original state.</p>
<p>For instance, I tried to show a back arrow in the header based on which page we were on. This was originally a <code>#if</code> block in the component looking at the current value of <code>$page.url</code>. However, this caused a race condition where the $page store updated before the transition started, causing the back icon to disappear too soon.</p>
<p>Similarly, I had to change how I set view transition names on the video embed and thumbnail elements. Trying to do so via style directives was unpredictable, and sometimes the styles would be applied too late. Instead, I <a href="https://github.com/geoffrich/http-203-svelte/blob/faf008d0b1c1914c8fcb6e5936c1f549a5647e21/src/lib/VideoList.svelte#L21-L62">set the style</a> on the transitioning DOM elements manually.</p>
<p>It’s unfortunate that we have to write our components in a less-idiomatic way, but the timing is tricky to get right.</p>
<p>But the worst race condition of the bunch was when I enabled SvelteKit’s <a href="https://kit.svelte.dev/docs/link-options#data-sveltekit-preload-data">preloading</a>. When I did that, the transitions would consistently break — SvelteKit was just changing the page too fast! And there’s no way to tell it to wait to navigate, since <code>beforeNavigate</code> is synchronous.</p>
<p>The only way I could fix it was manually cancelling the navigation and restarting it with <code>goto</code> so the transition had a chance to start. This has some unintended side effects if another listener is checking the navigation type, and also doesn’t work for history traversals (e.g. hitting the back button). It doesn’t work all the time, but it’s much more consistent than before.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token keyword">if</span> <span class="token punctuation">(</span>navigationType <span class="token operator">===</span> <span class="token string">'link'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> <span class="token number">0</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 punctuation">)</span> <span class="token operator">=></span> <span class="token function">goto</span><span class="token punctuation">(</span>to<span class="token operator">?.</span>url <span class="token operator">??</span> <span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Here’s <a href="https://github.com/geoffrich/http-203-svelte/blob/faf008d0b1c1914c8fcb6e5936c1f549a5647e21/src/lib/page-transition.js#L133-L142">the code snippet</a> in context. I’m not happy with the workaround I found for this and want to find a better solution — maybe there’s a better API for SvelteKit to expose here?</p>
<p>Anyway, it was super fun to get back to these demos! I definitely spent a bit more time on them than expected. I love the effect and look forward to this API being implemented by other browsers (still TBD).</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>How to git rebase on main without switching branches</title>
    <link href="https://geoffrich.net/posts/git-rebase-with-latest-changes/"/>
    <updated>2023-02-04T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/git-rebase-with-latest-changes/</id>
    
    <content type="html"><![CDATA[
      <p>This is just a quick TIL about updating your feature branch with changes from main without switching between branches.</p>
<p><strong>The problem:</strong> you’re working on some changes in a separate branch and want to update your branch with new changes from the main branch. How do you do it?</p>
<p>I used to take the long way round:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> checkout main<br><span class="token function">git</span> pull<br><span class="token function">git</span> checkout feature<br><span class="token function">git</span> rebase main</code></pre>
<p>However, I recently learned you can do this in two commands instead of four:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> fetch origin main:main<br><span class="token function">git</span> rebase main</code></pre>
<p>The first command fetches the latest changes from the remote and applies them to your local main branch. You then have the latest updates and can rebase your feature branch onto main (or merge, if you prefer).</p>
<p>Alternatively, if you’ve already fetched the latest changes (even if they haven’t been applied to your local main branch), you can do it in one command:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> rebase origin main</code></pre>
<p>This will rebase your branch onto the latest changes from the remote main branch even if they haven’t been applied to your local main branch yet.</p>
<p>I have my IDE set to automatically fetch the latest changes, so I may find myself doing the second version more often.</p>
<p>I shared this tip <a href="https://front-end.social/@geoffrich/109792203222668103">over on Mastodon</a> and went semi-viral (100+ boosts, 200+ favs) — well, as viral as a Mastodon post about <code>git rebase</code> can go.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Rate limit your SvelteKit app with Upstash Redis</title>
    <link href="https://upstash.com/blog/sveltekit-rate-limiting"/>
    <updated>2023-02-01T00:00:00Z</updated>
    <id>https://upstash.com/blog/sveltekit-rate-limiting</id>
    
    <summary>Posted on Upstash: </summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Advent of SvelteKit 2022: my favorite demos</title>
    <link href="https://geoffrich.net/posts/advent-of-sveltekit-2022/"/>
    <updated>2023-01-29T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/advent-of-sveltekit-2022/</id>
    
    <content type="html"><![CDATA[
      <p>Throughout December (and part of January) I did <a href="https://advent-of-sveltekit-2022.vercel.app/">Advent of SvelteKit 2022</a>, where I took the <a href="https://github.com/Advent-Of-Vue">Advent of Vue</a> challenges and did them in SvelteKit. Not satisfied with simply recreating the challenge in vanilla Svelte, I had a few extra goals for my solutions:</p>
<ul>
<li><strong>progressive enhancement:</strong> where possible, solutions should use native functionality (<code>&lt;a&gt;</code> and <code>&lt;form&gt;</code>) so they work without JavaScript and enhance when JavaScript is available.</li>
<li><strong>SSR-able:</strong> because you can't progressively enhance without starting with HTML, all solutions should be server-rendered (either at build or request time). SvelteKit enables this by default.</li>
</ul>
<p>Originally I thought I thought I'd write a post exhaustively recounting each demo. However...</p>
<ol>
<li>that's a lot of writing</li>
<li>very few people would read the whole post</li>
<li>and honestly, not <em>every</em> solution needs a detailed breakdown</li>
</ol>
<p>So instead, I'm going to highlight my favorite 5 demos and link to the others at the end. In this post we'll show a few interesting techniques, including:</p>
<ul>
<li>progressively enhancing a search form that requests search results as the user types</li>
<li>animating a details element open and closed with a Svelte action</li>
<li>how to safely use random numbers in server-rendered Svelte components</li>
<li>using recursion in Svelte components</li>
<li>building a media player that works without JavaScript</li>
</ul>
<p>One other minor change I made — the original solutions used Tailwind; I opted for <a href="https://open-props.style/">Open Props</a> and Svelte's scoped styles instead.</p>
<p>(Caveat: while I put a lot of time (too much?) into these, at the end of the day they are one-off demos that may not be ready for production. While I did my best, I haven't fully vetted the performance or accessibility of these solutions.)</p>
<h2>Day 1: Product search bar</h2>
<p><strong>The challenge:</strong> build a <a href="https://advent-of-sveltekit-2022.vercel.app/day/1">debounced search bar</a> for products using a dummy API.</p>
<img src="/images/advent-of-sveltekit-2022/day-1.png" alt="Screenshot of the product search demo. It shows a text input labelled 'Query' with the text 'phone' inside. Four results are displayed in a bulleted list." title="The product search demo. Not a lot to look at, I know." style="width: 100%; max-width: 600px">
<p>I used a <code>&lt;form&gt;</code> so that I could progressively enhance the experience. With JS unavailable, it will submit your search via query params and reload the page. With JS available, it will still set query params, but update the results client-side without a full page refresh.</p>
<p>Note that there's no <code>bind:value</code> in this solution, which would only work with JS. Instead, I used an <code>&lt;input name=&quot;q&quot;&gt;</code> inside a <code>&lt;form&gt;</code>. Submitting the form updates the URL and re-runs the load function, which passes the query param along to the external API. SvelteKit will <a href="https://kit.svelte.dev/docs/form-actions#get-vs-post">automatically enhance</a> <code>&lt;form method=&quot;get&quot;&gt;</code> so that the page is updated client-side, though I implemented a custom submit handler myself to 1) replace URL state (so each new query doesn't add a new history entry) and 2) keep focus on the search box. There is an <a href="https://github.com/sveltejs/kit/issues/7895">open feature request</a> to make customizing this easier.</p>
<p>But using a form, how do we automatically load results as the user types? I used a debounced <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit"><code>requestSubmit</code></a> (<em>not</em> <code>submit</code>) call, which will behave the same as if the user clicked a submit button themselves. This is in contrast to the <code>submit</code> method, which will only submit the form — it won't run validation or custom submit handlers and will force a full-page reload. Make sure to check browser support before using this method. At time of writing, it was only <a href="https://caniuse.com/?search=requestsubmit">recently supported in Safari</a> (16.0 and above), but a <a href="https://github.com/javan/form-request-submit-polyfill">polyfill</a> exists.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> debouncedSubmit <span class="token operator">=</span> <span class="token function">debounce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token comment">// not supported in all browsers</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> <span class="token class-name">HTMLFormElement</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>requestSubmit <span class="token operator">==</span> <span class="token string">'function'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    form<span class="token punctuation">.</span><span class="token function">requestSubmit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This is also a perfect case for using <code>+page.js</code> over <code>+page.server.js</code>, since we are calling a public external API. By putting it in the <a href="https://kit.svelte.dev/docs/load#universal-vs-server">universal load function</a> instead of the server load function, we can request the search results directly in the browser instead of passing the request through our app's server. Combined with SvelteKit <a href="https://kit.svelte.dev/docs/link-options#data-sveltekit-preload-data">preloading the data</a> for the next page of results on hover, navigating feels instant!</p>
<p>I also implemented a declarative loading state: instead of toggling a boolean, I used a reactive statement checking the value of SvelteKit's <code>navigating</code> store.</p>
<pre class="language-js"><code class="language-js"><span class="token literal-property property">$</span><span class="token operator">:</span> isLoading <span class="token operator">=</span> $navigating<span class="token operator">?.</span>to<span class="token operator">?.</span>url<span class="token punctuation">.</span>pathname <span class="token operator">===</span> $page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>pathname<span class="token punctuation">;</span></code></pre>
<h2>Day 2: Christmas joke generator</h2>
<p><strong>The challenge:</strong> build a <a href="https://advent-of-sveltekit-2022.vercel.app/day/2">Christmas joke generator</a> using the <a href="https://v2.jokeapi.dev/">JokeAPI</a></p>
<img src="/images/advent-of-sveltekit-2022/day-2.png" alt="Screenshot of the joke generator demo. It shows the following joke: Why does Santa have three gardens? So he can 'ho ho ho'!" title="The joke generator demo. I'm using the term 'joke' loosely." style="width: 100%; max-width: 600px">
<p>I was super happy with the progressive enhancement on the joke reveal here. I used a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details">details element</a> for the &quot;tell me!&quot; button so that the joke was revealable without JS. To show the next joke, I used a form with only a submit button. This worked for the JS-less case, but I needed to call <code>goto</code> directly with <code>invalidateAll</code> when JS was available. Otherwise SvelteKit wouldn't do anything because you're navigating to a page you're already on (this could be a bug, I'm not certain.)</p>
<p>One problem with using the details element is there isn't a smooth transition when it expands. I couldn't use Svelte or CSS transitions either, since everything was already in the DOM and you can't animate height easily with just CSS. Instead, I adapted the approach from <a href="https://css-tricks.com/how-to-animate-the-details-element-using-waapi/">this CSS-Tricks article</a> with <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/tree/main/src/routes/day/2/animate.ts">a Svelte action</a>. This is another example of progressive enhancement: when we have JS, the details animates. Otherwise, the content make sense without it.</p>
<p>Similar to the last one, we use <code>+page.ts</code> to load data so we can request it without passing through our server.</p>
<div class="callout">
<p>Brief aside: while it's great that details/summary works without JS, it's not an accessible replacement <a href="https://cloudfour.com/thinks/a-details-element-as-a-burger-menu-is-not-accessible/">for all use cases</a> where you need to hide/show content — sometimes you <em>need</em> JS to make a control accessible.</p>
</div>
<p>Here's the full <code>+page.svelte</code> (minus styles):</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> goto <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/navigation'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> navigating<span class="token punctuation">,</span> page <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/stores'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> type <span class="token punctuation">{</span> PageData <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./$types'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> Spinner <span class="token keyword">from</span> <span class="token string">'$lib/Spinner.svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> animate <span class="token keyword">from</span> <span class="token string">'./animate'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> <span class="token literal-property property">data</span><span class="token operator">:</span> PageData<span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> joke <span class="token operator">=</span> data<span class="token punctuation">.</span>joke<span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> open <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token parameter"><span class="token literal-property property">e</span><span class="token operator">:</span> SubmitEvent</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>		<span class="token keyword">const</span> form <span class="token operator">=</span> e<span class="token punctuation">.</span>target <span class="token keyword">as</span> HTMLFormElement<span class="token punctuation">;</span><br>		<span class="token keyword">await</span> <span class="token function">goto</span><span class="token punctuation">(</span>form<span class="token punctuation">.</span>action<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">invalidateAll</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">replaceState</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>		open <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment">// close the summary</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> isLoading <span class="token operator">=</span> $navigating<span class="token operator">?.</span>to<span class="token operator">?.</span>url<span class="token punctuation">.</span>pathname <span class="token operator">===</span> $page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>pathname<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span>Christmas Joke Generator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>setup<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>joke<span class="token punctuation">.</span>setup<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>wrapper<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span> <span class="token attr-name"><span class="token namespace">bind:</span>open</span> <span class="token attr-name"><span class="token namespace">use:</span>animate</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">></span></span>Tell me!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>flex content<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>delivery<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>joke<span class="token punctuation">.</span>delivery<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name"><span class="token namespace">on:</span>submit|preventDefault=</span><span class="token language-javascript"><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>				<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">disabled=</span><span class="token language-javascript"><span class="token punctuation">{</span>isLoading<span class="token punctuation">}</span></span><br>					<span class="token punctuation">></span></span>Another! 🎅<br>					<span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> isLoading<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Spinner</span> <span class="token punctuation">/></span></span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><br>				<span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<h2>Day 11: Recursive Christmas lights</h2>
<p><strong>The challenge:</strong> create a <a href="https://advent-of-sveltekit-2022.vercel.app/day/11">lit Christmas tree</a> using recursive components</p>
<img src="/images/advent-of-sveltekit-2022/day-3.png" alt="Screenshot of the Christmas lights demo. It shows a 7-layer Christmas tree made out of overlapping circles. Small yellow circles are also on the tree, representing Christmas lights." title="The Christmas lights demo. It just about convinces you that you're looking at a real tree." style="width: 100%; max-width: 600px">
<p>This was an interesting way to use <a href="https://svelte.dev/docs#svelte_self"><code>svelte:self</code></a>, which otherwise doesn't come up very often. The <code>&lt;ChristmasTree&gt;</code> component recursively renders itself, similar to how a recursive function calls itself.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- +page.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ChristmasTree</span> <span class="token attr-name">size=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token number">7</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ChristmasLights</span> <span class="token attr-name">random=</span><span class="token language-javascript"><span class="token punctuation">{</span>data<span class="token punctuation">.</span>rng<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ChristmasLights</span> <span class="token attr-name">random=</span><span class="token language-javascript"><span class="token punctuation">{</span>data<span class="token punctuation">.</span>rng<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ChristmasTree</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- ChristmasTree.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ts<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> fade <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> size <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> size <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">size=</span><span class="token language-javascript"><span class="token punctuation">{</span>size <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>self</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>tree<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">in:</span>fade=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">delay</span><span class="token operator">:</span> size <span class="token operator">*</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript"><span class="token punctuation">{</span> <span class="token literal-property property">length</span><span class="token operator">:</span> size <span class="token punctuation">}</span> </span><span class="token keyword">as</span> <span class="token language-javascript">_<span class="token punctuation">}</span></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>leaf<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>			<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>	<span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Each of the <code>&lt;ChristmasLights&gt;</code> components randomly place a single light. I was surprised to see that not only could you use a Svelte <code>&lt;slot&gt;</code> multiple times in the same component, each occurrence of the slot has a unique instance of the slotted component. Even though I passed two Christmas light components into the tree's slot, there are more than two Christmas lights being rendered, and all have their own state. If this weren't true, each light would be in the same spot on each leaf, which is clearly not the case.</p>
<p>Another interesting challenge was getting random numbers to work with SSR. In purely-client-side Svelte, you can use <code>Math.random</code> without any consequences. However, with server-rendered Svelte, your component code runs twice: once on the server, and once during hydration. The second time your component code runs, the random number will be different and your UI will appear to &quot;jump&quot; to the new position. I ran into this issue with the lights, which used <code>Math.random</code> to place themselves randomly.</p>
<p>The way to fix this is to use a <a href="https://github.com/davidbau/seedrandom">seeded random number generator</a>, which given the same seed value, will generate the same list of random numbers. But then we run into a different problem: how do we generate a random seed? We could hard-code one, but then there would be no randomness — the lights would be placed in the same random position on every page load.</p>
<p>The solution I landed on could be a blog post by itself, but in short:</p>
<ul>
<li>generate a seed based on the current date in the root <code>+layout.server.ts</code>. By doing this in the root layout, it will only run once, so we don't unnecessarily hit the server to get a new RNG seed on each navigation. We need to do this in the server layout since <code>+layout.ts</code> will run again during hydration.</li>
<li>then, <code>+layout.ts</code> uses that seed to create a random number generator (RNG). Any page can access this RNG via the <code>data</code> prop</li>
</ul>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> type <span class="token punctuation">{</span>LayoutLoad<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./$types'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> seedrandom <span class="token keyword">from</span> <span class="token string">'seedrandom'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">load</span><span class="token operator">:</span> <span class="token function-variable function">LayoutLoad</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>data<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token comment">// expose random number generator to be used in Day 11</span><br>    <span class="token comment">// we generate the seed on the server because the universal load runs twice</span><br>    <span class="token literal-property property">rng</span><span class="token operator">:</span> <span class="token function">seedrandom</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>seed<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Now that we have a seeded RNG, we can pass that function to the <code>&lt;ChristmasLights&gt;</code> component to use instead of <code>Math.random</code>. You can tell that it's working by refreshing the demo page and seeing that the christmas lights stay where they are without jumping. Because the server- and client-render use the same seed, the component code generates the same random values.</p>
<p>This challenge was part 2 of a series of Christmas-tree-focused challenges that built on one another. The first was <a href="https://advent-of-sveltekit-2022.vercel.app/day/4">just the tree</a> (no lights), and <a href="https://advent-of-sveltekit-2022.vercel.app/day/13">the third part</a> added ornaments.</p>
<div class="callout">
<p>For more on svelte:self, see <a href="/posts/svelte-tower-of-hanoi/">this blog post</a> from a couple years back where I solved the classic &quot;Tower of Hanoi&quot; problem only using Svelte's template syntax.</p>
</div>
<h2>Day 14: Secret santa list generator</h2>
<p><strong>The challenge:</strong> build an app that will let you add names to a list and pair everyone up for <a href="https://advent-of-sveltekit-2022.vercel.app/day/14">Secret Santa</a>.</p>
<img src="/images/advent-of-sveltekit-2022/day-14a.png" alt="Screenshot of the Secret Santa demo. It shows a form where you can enter Name and Email. Two names are displayed under the form: Kevin McCallister and John McClane. Each name has a delete button next to it." title="The Secret Santa entry form. I think purple's a very Christmassy color, don't you?" style="width: 100%; max-width: 600px">
<img src="/images/advent-of-sveltekit-2022/day-14b.png" alt="Screenshot of the Secret Santa demo. It shows a list of 5 Secret Santa matches, e.g. Kevin McCallister is the Secret Santa of John McClane. There are two buttons below the matches: Shuffle and Go back." title="The Secret Santa matches. I wouldn't want the Grinch to be my Secret Santa, but maybe that's just me." style="width: 100%; max-width: 600px">
<p>This was a perfect challenge to take SvelteKit <a href="https://kit.svelte.dev/docs/form-actions">form actions</a> for a spin! Pretty much every button on here triggers a progressively-enhanced form submission. The <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/blob/main/src/routes/day/14/%2Bpage.server.ts">server code</a> doesn't show on the demo page, so head over to GitHub if you're curious about the implementation.</p>
<p>The list of names is stringified and stored in a cookie so the server can access it. This was good enough for a POC where I didn't want to figure out a data layer, but it did cause some issues with concurrent updates. This is because the list of names is sent in a cookie with each request, so it doesn't get updated as other requests complete and it's possible to return stale data.</p>
<p>Typing in names again and again when testing got old quickly, so I added a &quot;pre-fill&quot; button powered by <a href="https://fakerjs.dev/">Faker</a>.</p>
<p>This was also my first time taking <a href="https://github.com/colinhacks/zod">Zod</a> for a spin, which was very nice for parsing and validating the data in the cookie. Any Zod validation errors were caught and returned for the UI to render.</p>
<p>I also added some extra animation polish as elements were added and removed with Svelte's built-in FLIP animation and transitions. It's nice that this is only an extra line or two in Svelte instead of needing to reach for a separate library.</p>
<h2>Day 15: Christmas radio</h2>
<p><strong>The challenge:</strong> build a <a href="https://advent-of-sveltekit-2022.vercel.app/day/15">media player</a> with a list of Christmas-themed tracks</p>
<img src="/images/advent-of-sveltekit-2022/day-15.png" alt="Screenshot of the Christmas radio demo. Five tracks are shown: Snow Mountain, In a Snow-Bound Land, Snowman, Snowpoint City, and Snowhead Temple. Below the list of tracks are audio player controls." title="The Christmas radio demo. Of these, Snow Mountain is probably the most holiday-like song. Snowhead Temple shouldn't be on anyone's Christmas playlist." style="width: 100%; max-width: 600px">
<p>Instead of the stock audio tracks in the original challenge, I used snow-themed video game music songs.</p>
<p>This was slightly more challenging than the Vue version, since I didn't have <a href="https://vueuse.org/core/usemediacontrols/">useMediaControls</a> and had to implement a lot from scratch (though Svelte's <a href="https://svelte.dev/docs#template-syntax-element-directives-bind-property-media-element-bindings">media bindings</a> got me pretty far.) There were a lot of weird issues I ran into when running this demo on different browsers. For example:</p>
<ul>
<li>the audio element's <code>paused</code> binding got out-of-sync when the <code>src</code> changed</li>
<li>you can't set <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volume">media volume</a> on iOS. Per MDN: &quot;volume returns a value and is writable; however, the value is always 1, and setting a value has no effect on the volume of the media object.&quot;</li>
<li>duration was not being set correctly when the audio element was rendered on the server</li>
</ul>
<p>For all my hacky workarounds, see <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/blob/main/src/routes/day/15/Controls.svelte">Controls.svelte</a>. I'm pretty happy with where it ended up though.</p>
<p>Initially, I didn't make this work without JS (partly because of all the trouble I had working with audio elements). But the people on Twitter (well, one person) requested it and I couldn't stop thinking about it, so I got something working:</p>
<ul>
<li>the next/previous controls are form submit buttons that set the <code>current</code> query parameter. This is used to set the current track when the page loads.</li>
<li>each selectable song is a link that also sets the current track (though in hindsight, I think using a button here may have been more accessible)</li>
<li>buttons that only work when JS is enabled (e.g. fast-forward/rewind) are hidden when JS is disabled (detected via an inline script at the top of the body)</li>
<li>and if JS is disabled, I rendered an <code>&lt;audio controls autoplay&gt;</code> element so that the audio element can be interacted with using the browser controls</li>
</ul>
<p>The experience is much better with JS enabled, but it works.</p>
<h2>And the rest</h2>
<p>Here's a list of the other demos — click on whatever you find interesting and let me know if you have questions! Each page has a link to my solution's source code and original challenge. All the solutions are in the <a href="https://github.com/geoffrich/advent-of-sveltekit-2022">advent-of-sveltekit-2022 repo</a> on my GitHub.</p>
<ul>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/0">tic-tac-toe game</a>, which I covered in a <a href="/posts/tic-tac-toe/">previous post</a>. I was tempted to make this one work without JS like the SvelteKit Sverdle demo but didn't make time for it.</li>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/3">countdown to Christmas</a>. The tricky bit of this one was getting the remaining time to server-side-render properly. I also really like the &quot;rolling&quot; animation as each digit counts down (made possible with CSS grid and a <a href="https://svelte.dev/docs#template-syntax-key">key block</a>)</li>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/5">gift label generator</a> and a <a href="https://advent-of-sveltekit-2022.vercel.app/day/6">gift price comparer</a></li>
<li><a href="https://advent-of-sveltekit-2022.vercel.app/day/7">drag 'n' drop the presents</a> under the Christmas tree using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API">HTML drag and drop API</a></li>
<li>an internationalized <a href="https://advent-of-sveltekit-2022.vercel.app/day/8">happy holidays message</a> using Ivan Hofer's <a href="https://github.com/ivanhofer/typesafe-i18n">typesafe-i18n</a> library as an alternative to vue-i18n. Ivan was kind enough to open <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/pull/1">a PR</a> against my repo with suggested improvements. Of special interest is the <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/tree/main/src/routes/day/8/WrapTranslation.svelte">WrapTranslation</a> component I came up with, which lets you replace a certain portion of a translation with HTML (similar to vue-i18n's <a href="https://vue-i18n.intlify.dev/guide/advanced/component.html">i18n-t</a> element).</li>
<li><a href="https://advent-of-sveltekit-2022.vercel.app/day/9">sorting presents</a> with some extra polish via <code>animate:flip</code></li>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/10">Secret santa challenge</a>, where I give three clues and you have to guess who I am. I used <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/blob/main/src/routes/day/10/%2Bpage.server.ts">a SvelteKit form action</a> for the final submission and Zod for validating the input.</li>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/12">gift puzzle visualization</a>. This ended up being a visualization of the <a href="https://en.wikipedia.org/wiki/Josephus_problem">Josephus problem</a> and my solution was wrong... oh well. One interesting thing about this solution is that I moved all the component logic into the <code>+page.ts</code> load function, so <code>+page.svelte</code> is a pure view layer that submits forms.</li>
<li>a <a href="https://advent-of-sveltekit-2022.vercel.app/day/17">renderless component</a> for calculating distance from the North Pole. The distance component doesn't render UI — it runs some logic and passes props to a slot. The consumer of the component can read those props and decide how to use them. This is another one that needs JS to run. While I could approximate location from IP address on the server, it seemed best to ask the user's permission for location data on the client first.</li>
</ul>
<h2>The site itself</h2>
<p>I also had a fun time adding features to the site that hosted my solutions. For instance, I added <code>animate:flip</code> to the navigation above each challenge for that extra bit of polish.</p>
<p>I also implemented a custom layout load function that loads the raw source code for Svelte components in the current route (using Vite's <a href="https://vitejs.dev/guide/features.html#glob-import"><code>import.meta.glob</code></a>), so that I can display the code behind the solution on each page. I wasn't able to load the <code>+page.server.ts</code> code (since SvelteKit blocks you from importing server code on the client), but all of the other code is formatted using Prism and prism-svelte and shown under each solution.</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// src/routes/day/+layout.ts</span><br><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> LayoutLoad <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./$types'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> globs <span class="token operator">=</span> <span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span><span class="token function">glob</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./**/*.{svelte,js,ts}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token string">'!**/*.server.{js,ts}'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token keyword">as</span><span class="token operator">:</span> <span class="token string">'raw'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> load<span class="token operator">:</span> <span class="token function-variable function">LayoutLoad</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> url<span class="token punctuation">,</span> route <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>	<span class="token keyword">const</span> segments <span class="token operator">=</span> route<span class="token punctuation">.</span>id<span class="token operator">?.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token keyword">const</span> day <span class="token operator">=</span> segments<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> code<span class="token operator">:</span> <span class="token punctuation">{</span> filename<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> source<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>	<span class="token keyword">const</span> modules <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>globs<span class="token punctuation">)</span><br>		<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">[</span>k<span class="token punctuation">,</span> v<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> k<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">./</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>day<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 operator">&amp;&amp;</span> <span class="token operator">!</span>k<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'.server'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br>		<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">[</span>k<span class="token punctuation">,</span> v<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span><br>			<span class="token function">v</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>result<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>				<span class="token keyword">const</span> segments <span class="token operator">=</span> k<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>				<span class="token keyword">return</span> <span class="token punctuation">{</span> filename<span class="token operator">:</span> segments<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><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 punctuation">,</span> source<span class="token operator">:</span> result <span class="token punctuation">}</span><span class="token punctuation">;</span><br>			<span class="token punctuation">}</span><span class="token punctuation">)</span><br>		<span class="token punctuation">)</span><span class="token punctuation">;</span><br>	code <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span>modules<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">return</span> <span class="token punctuation">{</span><br>		day<span class="token operator">:</span> <span class="token operator">+</span>day<span class="token punctuation">,</span><br>		code<br>	<span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span><br><br><span class="token comment">// this is then accessible via data.code in the +layout.svelte</span></code></pre>
<h2>Wrapping up</h2>
<p>I had a ton of fun with these challenges and I'll definitely continue to use forms and progressive enhancement techniques in my other work. In fact, my <a href="/posts/marvel-filter-state/">previous post</a> details how I applied some of the lessons I learned here to a search form in one of my side projects.</p>
<p>Also, I'm not the only one who tackled the Advent of Vue challenges in Svelte — check out <a href="https://twitter.com/search?q=from%3Apaoloricciuti%20adventofvue&amp;src=typed_query">Paolo Ricciuti</a> and <a href="https://twitter.com/search?q=from%3ASarcevicAntonio%20advent%20of%20vue&amp;src=typed_query&amp;f=top">Antonio Sarcevic</a> for alternate implementations. And if you also did them, let me know and I'll link you too!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Progressively enhancing the Marvel By Year filter</title>
    <link href="https://geoffrich.net/posts/marvel-filter-state/"/>
    <updated>2023-01-16T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/marvel-filter-state/</id>
    
    <content type="html"><![CDATA[
      <p>Marvel Unlimited By Year (a.k.a. MUBY) is a <a href="/posts/marvel-unlimited-by-year/">site I built</a> to browse Marvel comics by release year. For example, this is <a href="https://marvel.geoffrich.net/year/1982">all the comics released in 1982</a> that you can read on Marvel Unlimited. Each page has a set of inputs that let you filter which comics are shown. You can filter issues by title, release month, series, creator, and event, and sort them in various orders.</p>
<img src="/images/marvel-filter-state/filter.png" alt="Screenshot of the MUBY filter UI" title="What the filter currently looks like">
<p>These filters only worked on the client-side — you couldn't use them if you didn't have JS enabled, and any filters you applied would be lost when you refreshed the page. There was also a particular gnarly setup to keep track of selected creators/events/series across page loads — see the <a href="#heading-persistence">persistence</a> section further down for more details on that.</p>
<p>I recently did a lot of progressive enhancement experiments with <a href="https://advent-of-sveltekit-2022.vercel.app/">Advent of SvelteKit 2022</a> and wanted to bring over some of those techniques to this site. My goals were:</p>
<ol>
<li>Move filter state into the URL to make sharing and persisting filter state easier</li>
<li>Use web platform primitives like <code>&lt;form&gt;</code> so that the site still works when JS is unavailable</li>
</ol>
<p>(Note that this particular site doesn't call an API to do any filtering — I just get the raw data from the Marvel API and then implement any filtering needed myself.)</p>
<p>Here's a quick overview of what I did. Some of these techniques may be expanded into full posts in the future.</p>
<p>You can find the <a href="https://github.com/geoffrich/marvel-by-year/pull/13">full PR</a> on GitHub if you're really curious.</p>
<h2>Goodbye bind:value, hello &lt;input name&gt;</h2>
<p>To meet my goals, instead of reading the values of the inputs with <a href="https://svelte.dev/docs#template-syntax-element-directives-bind-property">Svelte input bindings</a> and updating the state of the page accordingly, I needed to put them in a <code>&lt;form&gt;</code> and give them <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name">names</a> so that the values would be submitted with the form.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span>Search <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>search<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Select</span><br>        <span class="token attr-name">options=</span><span class="token language-javascript"><span class="token punctuation">{</span>sortOptionText<span class="token punctuation">}</span></span><br>        <span class="token attr-name">values=</span><span class="token language-javascript"><span class="token punctuation">{</span>sortingOptions<span class="token punctuation">}</span></span><br>        <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sorting<span class="token punctuation">"</span></span><br>        <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sortBy<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Sort by<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Select</span><br>    <span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Select</span> <span class="token attr-name">options=</span><span class="token language-javascript"><span class="token punctuation">{</span>months<span class="token punctuation">}</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>month<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>month<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Release Month<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Select</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ascending<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>Ascending<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span></code></pre>
<p>Since the form's method was GET, submitting the form would put the values of the inputs in the URL as query parameters (goal #1), and because it was a form, it would work without JS (goal #2).</p>
<p>I then needed to get the values out of the URL to use them to update the UI. In SvelteKit, this can be implemented as a reactive statement reading <code>$page.url.searchParams</code>, which holds the page's current query params. I'll show how I made this type-safe in a later section.</p>
<pre class="language-js"><code class="language-js"><span class="token literal-property property">$</span><span class="token operator">:</span> search <span class="token operator">=</span> $page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'search'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> sortBy <span class="token operator">=</span> $page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'sortBy'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token comment">// and so on</span></code></pre>
<p>But with this initial implementation, we introduced a UX regression — the page no longer updates as you fill in the form and toggle checkboxes. Instead, you have to click &quot;Submit&quot;.</p>
<h2>Automatically submitting the form with requestSubmit</h2>
<p>To fix this, I reached for a method I used on quite a few Advent of SvelteKit challenges: <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit">requestSubmit</a>. Unlike the normal <code>submit</code> function which only submits the form (and triggers a full-page refresh), <code>requestSubmit</code> will behave identically to the user clicking the submit button: it will run validation, custom submit handlers, and other goodies.</p>
<p>So, by calling <code>requestSubmit</code> on every form change, we'll automatically update the page and filter the displayed comics without the user needing to click &quot;Submit&quot;. It ended up looking something like this:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> form<span class="token punctuation">;</span><br>	<span class="token keyword">const</span> <span class="token function-variable function">requestSubmit</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> form<span class="token punctuation">.</span><span class="token function">requestSubmit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token keyword">const</span> debouncedSubmit <span class="token operator">=</span> <span class="token function">debounce</span><span class="token punctuation">(</span>requestSubmit<span class="token punctuation">,</span> <span class="token number">250</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span><br>	<span class="token attr-name"><span class="token namespace">bind:</span>this=</span><span class="token language-javascript"><span class="token punctuation">{</span>form<span class="token punctuation">}</span></span><br>	<span class="token attr-name"><span class="token namespace">on:</span>input=</span><span class="token language-javascript"><span class="token punctuation">{</span>debouncedSubmit<span class="token punctuation">}</span></span><br>	<span class="token attr-name"><span class="token namespace">on:</span>change=</span><span class="token language-javascript"><span class="token punctuation">{</span>requestSubmit<span class="token punctuation">}</span></span><br><span class="token punctuation">></span></span></code></pre>
<p>For input events, I called a debounced version of the submit (using the zero-dependency utility library <a href="https://github.com/angus-c/just#just-debounce-it">Just</a>) that only fired every 250ms. This way we don't run the filter logic on every keypress; instead, we give the user a chance to finish typing. Since changing a select or checkbox are single actions, we'll immediately submit the form on change events.</p>
<p>But this introduced a new problem — SvelteKit will <a href="https://kit.svelte.dev/docs/form-actions#get-vs-post">automatically trigger a client-side navigation</a> when you submit a <code>&lt;form method=&quot;GET&quot;&gt;</code>, but it will also reset the page focus and add a new entry to the browser history. In this case, I didn't want that:</p>
<ol>
<li>We don't want to move the user's focus away from the text box when they're still typing</li>
<li>We also don't want a new browser history entry for partial search strings. If they're typing &quot;Black Panther,&quot; we don't want history entries for &quot;Bla&quot;, &quot;Black P&quot;, and &quot;Black Panther&quot;.</li>
</ol>
<p>There is a <a href="https://github.com/sveltejs/kit/issues/7895">feature request</a> to allow customizing this behavior on <code>&lt;form method=&quot;GET&quot;&gt;</code>, but for now, I implemented a custom submit handler that keeps focus and replaces the current history entry when submitting the form.</p>
<pre class="language-ts"><code class="language-ts"><span class="token comment">// add to form with on:submit={submitReplaceState}</span><br><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">submitReplaceState</span><span class="token punctuation">(</span>e<span class="token operator">:</span> SubmitEvent<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> form <span class="token operator">=</span> e<span class="token punctuation">.</span>target <span class="token keyword">as</span> HTMLFormElement<span class="token punctuation">;</span><br>  <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token constant">URL</span></span><span class="token punctuation">(</span>form<span class="token punctuation">.</span>action<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token comment">// @ts-expect-error</span><br>  <span class="token keyword">const</span> params <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FormData</span><span class="token punctuation">(</span>form<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  url<span class="token punctuation">.</span>search <span class="token operator">=</span> params<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token function">goto</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token punctuation">{</span>replaceState<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> keepFocus<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> noScroll<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>It's worth noting that <code>requestSubmit</code> was only <a href="https://caniuse.com/?search=requestsubmit">recently supported in Safari</a> (16.0 and above), so I ended up <a href="https://github.com/javan/form-request-submit-polyfill">polyfilling it</a>. You could also consider falling back to <code>submit</code>, though keep in mind that will trigger a full-page reload if they don't support <code>requestSubmit</code>.</p>
<h2>+page.ts or nah?</h2>
<p>I briefly experimented with moving the query parameter parsing logic inside a <code>+page.ts</code> load function (a.k.a. <a href="https://kit.svelte.dev/docs/load#universal-vs-server">universal load function</a>). I wanted to keep it out of <code>+page.svelte</code> to avoid making that file too messy. Instead, the <code>load</code> function would return a <code>filter</code> object (parsed from the list of query params) that represented the state of the form. However, I wanted to make sure that while doing this I was <em>not</em> refetching the list of comics in the server load function on every form change, since that was a lot of data and would be wasteful.</p>
<p>Surprisingly, this worked — the universal load function re-ran when the query parameters changed, and it did not re-run the server load function in <code>+page.server.ts</code>. However, it did have an unintended side effect. Because the universal load function re-ran, it returned a new <code>data</code> object, and all the reactive statements that used that <code>data</code> object <em>also</em> re-ran. In particular, the code to transform the giant list of comics into a list of unique creators/series/events on those comics was re-running on every form change, which seemed wasteful.</p>
<p>Because of this, I kept the query param parsing logic inside <code>+page.svelte</code>, which seemed to work fine and did not invalidate the entire page <code>data</code> object. (The query param parsing logic will become one line in a later section, so the impact on <code>+page.svelte</code> length is negligible.)</p>
<h2>Checkbox weirdness</h2>
<p>Turning everything into a real form input did bring some weirdness with it. For instance, before these changes, I had a &quot;Descending&quot; checkbox that defaulted to &quot;checked&quot;. However, I couldn't figure out a good way to default it to checked — if it was checked by default, but not in the URL, then how would we determine if we're in an actual &quot;unchecked&quot; state? It wasn't that big a deal, so I flipped it to be &quot;Ascending&quot; instead and default to unchecked.</p>
<p>Also, the creator/series/event filters had a massive number of checkboxes that used to be enabled by default. Now that they're in a form, submitting that form would make the URL absolutely ginormous, since each checkbox would appear as a query param (e.g. &quot;&amp;creator=1&amp;creator=2&amp;creator=3...&quot;).</p>
<p>To work around this, I updated the filter checkboxes to be unchecked by default, and for the UI to treat &quot;no creators selected&quot; the same as &quot;all creators selected.&quot; This meant there wasn't an easy way to select all but one creator, but I don't think that's a common use case.</p>
<p>Now instead of &quot;Check all&quot; and &quot;Uncheck all&quot; buttons, there's just one &quot;Select all&quot; button that unchecks anything.</p>
<h2>Type-safe query param parsing with zod-form-data</h2>
<p>So everything is a query param now, which is <em>cool</em> for progressive enhancement, but everything being a query param means everything is also a string, which is <em>bad</em> for type safety. Enter: <a href="https://github.com/colinhacks/zod">Zod</a>, which will parse our search params, validate that they adhere to a given schema, and return a fully typed object. I also used <a href="https://www.npmjs.com/package/zod-form-data">zod-form-data</a> (which was written for Remix, but can be used here since SvelteKit also uses web Request/URL objects) to make parsing URLSearchParams easier.</p>
<p>Here's what my schema ended up looking like:</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span>z<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'zod'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>zfd<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'zod-form-data'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> SortOptionEnum <span class="token operator">=</span> z<span class="token punctuation">.</span><span class="token function">enum</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'BestMatch'</span><span class="token punctuation">,</span> <span class="token string">'Title'</span><span class="token punctuation">,</span> <span class="token string">'PublishDate'</span><span class="token punctuation">,</span> <span class="token string">'UnlimitedDate'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> MonthEnum <span class="token operator">=</span> z<span class="token punctuation">.</span><span class="token function">enum</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'all'</span><span class="token punctuation">,</span> <span class="token string">'Jan'</span><span class="token punctuation">,</span> <span class="token string">'Feb'</span><span class="token punctuation">,</span> <span class="token string">'Mar'</span> <span class="token comment">/* etc. */</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> SortOption <span class="token operator">=</span> SortOptionEnum<span class="token punctuation">.</span>enum<span class="token punctuation">;</span><br><span class="token keyword">export</span> <span class="token keyword">const</span> Month <span class="token operator">=</span> MonthEnum<span class="token punctuation">.</span>enum<span class="token punctuation">;</span><br><br><span class="token keyword">const</span> multiCheckbox <span class="token operator">=</span> zfd<span class="token punctuation">.</span><span class="token function">repeatable</span><span class="token punctuation">(</span>z<span class="token punctuation">.</span><span class="token function">array</span><span class="token punctuation">(</span>zfd<span class="token punctuation">.</span><span class="token function">numeric</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> filterSchema <span class="token operator">=</span> zfd<span class="token punctuation">.</span><span class="token function">formData</span><span class="token punctuation">(</span><span class="token punctuation">{</span><br>  search<span class="token operator">:</span> zfd<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span>z<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">default</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br>  ascending<span class="token operator">:</span> zfd<span class="token punctuation">.</span><span class="token function">checkbox</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br>  <span class="token comment">// use catch in case no value or bad value passed</span><br>  sortBy<span class="token operator">:</span> zfd<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span>SortOptionEnum<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token string">'BestMatch'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br>  month<span class="token operator">:</span> zfd<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span>MonthEnum<span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token string">'all'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span><br>  series<span class="token operator">:</span> multiCheckbox<span class="token punctuation">,</span><br>  creator<span class="token operator">:</span> multiCheckbox<span class="token punctuation">,</span><br>  event<span class="token operator">:</span> multiCheckbox<br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Which I could then use like so on the page:</p>
<pre class="language-js"><code class="language-js"><span class="token literal-property property">$</span><span class="token operator">:</span> filter <span class="token operator">=</span> filterSchema<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>$page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>searchParams<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Then <code>filter</code> would be something like this object, which I could use in my various filtering/sorting functions:</p>
<pre class="language-js"><code class="language-js"><span class="token punctuation">{</span><br>    <span class="token literal-property property">search</span><span class="token operator">:</span> <span class="token string">"spiderman"</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">ascending</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">sortBy</span><span class="token operator">:</span> <span class="token string">'BestMatch'</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">month</span><span class="token operator">:</span> <span class="token string">'all'</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">series</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">creator</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">event</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">67</span><span class="token punctuation">]</span><br><span class="token punctuation">}</span></code></pre>
<p>I made liberal use of <a href="https://github.com/colinhacks/zod#catch">catch</a> to provide default values when the data couldn't be parsed.</p>
<p>This was extremely helpful to catch bugs when I was refactoring existing logic, since the new search params had defined types instead of just being a string. Some of the zod-form-data constructs were a little wordy, but I'm not sure if that's because I was using it incorrectly. I did run into <a href="https://github.com/airjp73/remix-validated-form/issues/230">a bug</a> using ZFD with Vite, which I was able to work around by putting it in <a href="https://vitejs.dev/config/ssr-options.html#ssr-noexternal">noExternal</a>.</p>
<p>I'm pretty new to Zod, but look forward to exploring it more in the future.</p>
<h2>Persistence</h2>
<p>Now since everything is a query param, all I have to do to persist the filter state across different pages is append the query params to the next/prev links:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href="</span>/<span class="token attr-name">year</span>/<span class="token language-javascript"><span class="token punctuation">{</span>year <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token language-javascript"><span class="token punctuation">{</span>$page<span class="token punctuation">.</span>url<span class="token punctuation">.</span>search <span class="token operator">||</span> <span class="token string">''</span><span class="token punctuation">}</span></span><span class="token attr-name">"</span><span class="token punctuation">></span></span>Next year<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre>
<p>This also simplified the code around persisting the series/creator/event checkbox state between pages. This has been a tricky part of the code historically, since the checkbox options for those filters are completely different between pages — 1982 will have different series released than 1983 (though with some overlap). But I didn't want to reset the selected filters between pages. Ideally if you have &quot;Avengers&quot; selected on 1982 and click on to 1983, &quot;Avengers&quot; should still be selected.</p>
<p>The previous iteration of this code had a complicated custom store factory setup that kept track of the previous selected state and updated it to 1) remove any selected options not present on the new page and 2) keep selected options around that are on the new page. I didn't really want to touch this code now that everything is a query parameter. Instead, I wanted to see what happened if I simply forwarded on the selected query parameters and derive the new filter state based on that (solving requirement #2). So if you have ?series=2&amp;series=3 in the URL, going on to the next page would keep those in the URL. And... this pretty much worked!</p>
<p>I don't have to worry about selected options not present on the new page — when the user selects another option, it will only put the new option in FormData (since this is how forms work) and not keep the old one around.</p>
<p>I probably could have simplified the code in a similar way without forms, but &quot;thinking in FormData&quot; unlocked this simpler solution for me.</p>
<h2>Submitting when no JS available</h2>
<p>Lastly, we automatically submit the form when JS is available, but what about when it's not? On the text input you can hit &quot;Enter&quot; to submit the form and reload the page, but this doesn't work well with the checkboxes — you would have to select a checkbox, focus the text input, and hit &quot;Enter&quot;. This isn't a great experience. It would be much better if we had an actual submit button.</p>
<p>However, I didn't want a submit button when JS is available, because it wouldn't do anything since the form is automatically submitted.</p>
<p><strong>EDIT:</strong> as pointed out by Jayphen <a href="https://twitter.com/Jayphen/status/1615406708997689372">on Twitter</a>, we can short-circuit the rest of the section by wrapping the button in a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript">noscript</a> tag. Leaving the rest as an alternate approach (and you could use the classes for more fine-grained control of what happens when JS is unavailable).</p>
<p>One option would be to have a submit button on the page for the initial server render and then hide it using <a href="https://svelte.dev/docs#run-time-svelte-onmount">onMount</a> or checking <a href="https://kit.svelte.dev/docs/modules#$app-environment-browser">browser</a>. Unfortunately, this could be jarring, since the submit button would render and then abruptly vanish when the page hydrates.</p>
<p>Instead, I added a short inline script to the top of the <code>&lt;body&gt;</code> in <code>app.html</code> that will run before anything renders. This toggles the <code>no-js</code> and <code>has-js</code> classes on the body.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span> <span class="token attr-name">data-sveltekit-preload-data</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>hover<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>no-js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>    document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'has-js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'no-js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>svelte<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>%sveltekit.body%<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span></code></pre>
<p>I added some global utility classes to hide or show elements based on JS availability:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">body.has-js .no-js-only</span> <span class="token punctuation">{</span><br>  <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token selector">body.no-js .js-only</span> <span class="token punctuation">{</span><br>  <span class="token property">display</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>And then we can add a submit button like this, and it will only show when JS is disabled:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>no-js-only<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Submit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>Like anything progressive enhancement, this is about tradeoffs. This won't have a FOMB (flash of misplaced button), but it will also hide the button when JS is enabled but not available yet (i.e. the page is hydrating).</p>
<h2>Wrapping up</h2>
<p>So that's how I spent my three-day-weekend — let me know if you have any feedback or suggestions!</p>
<p>Progressive form enhancement with SvelteKit is a topic I'm very interested in, so expect more content from me on that topic in the future.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Game music 2022 survey playlist</title>
    <link href="https://geoffrich.net/posts/game-music-2022/"/>
    <updated>2023-01-09T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/game-music-2022/</id>
    
    <content type="html"><![CDATA[
      <p>We'll get back to our regularly-scheduled webdev posts soon (🤞), but first I wanted to post about a non-dev side project I've been working on: a giant playlist surveying the video game music releases of 2022! This was very much inspired by the exhaustive <a href="http://www.fluxblog.org/">Fluxblog</a> survey playlists for pop music, though I didn't pay any attention to the sequencing of tracks.</p>
<p>It focuses on breadth instead of depth - 300+ tracks from 170+ games, including genres as varied as epic orchestral (God of War: Ragnorok), vibey electronica (Citizen Sleeper), Queen-inspired rock opera (Air Twister), and Peruvian folk music (Imp of the Sun). I tried to get as many 2022 game soundtracks on here as I could (at least what was available on streaming)!</p>
<ul>
<li><a href="https://open.spotify.com/playlist/61uaRcoGNX4ijkvTbM0auL">Spotify</a></li>
<li><a href="https://music.apple.com/us/playlist/game-music-2022/pl.u-b3b8MBgtLWa9dY">Apple Music</a></li>
</ul>
<p>Special shout out to the following resources where I sourced soundtracks from, as well as trawling GOTY 2022 lists:</p>
<ul>
<li><a href="https://acloserlisten.com/2022/12/12/press-a-2022-the-years-best-videogame-soundtracks/">A Closer Listen</a> - The Year's Best Videogame Soundtracks</li>
<li><a href="https://www.youtube.com/watch?v=5xMNjXwRm54">The MinnMax Show</a> - Our Favorite Game Music From 2022</li>
<li><a href="https://peterline6.substack.com/p/best-of-2022">NOWPLAYING</a> - Best of 2022</li>
<li><a href="https://www.videogameschronicle.com/features/these-are-the-best-game-soundtracks-of-2022-according-to-composers/">VGC</a> - These are the best game soundtracks of 2022, according to composers</li>
</ul>
<p>Some of my favorite tracks:</p>
<ul>
<li>&quot;Praise the Lamb&quot;, Cult of the Lamb</li>
<li>&quot;Yearning to Bloom&quot;, Mario + Rabbids Sparks of Hope</li>
<li>&quot;Once in a Lullaby&quot;, Risk of Rain 2: Survivors of the Void</li>
<li>&quot;Journey to Candle Hill&quot;, WaveTale</li>
<li>&quot;The Weight of Rain&quot;, TUNIC</li>
<li>&quot;Circling&quot;, Far: Changing Tides</li>
</ul>
<p>And while they aren't on the publicly-available versions of this playlist (or available for purchase/streaming), I also greatly enjoyed the soundtracks to Kirby and the Forgotten Land, Xenoblade Chronicles 3, and Pokemon Scarlet/Violet.</p>
<p>Here's a list of all the songs for posterity (#OwnYourContent):</p>
<details>
<summary>Full playlist contents</summary>
<div style="overflow: auto">
<table>
<thead>
<tr>
<th>Title</th>
<th>Artist</th>
<th>Album</th>
</tr>
</thead>
<tbody>
<tr>
<td>Nostalgic Idol</td>
<td>Keisuke Ito</td>
<td>Ai: The Somnium Files –Nirvana Initiative– (Complete Soundtrack)</td>
</tr>
<tr>
<td>Air Twister</td>
<td>Valensia</td>
<td>Air Twister (Original Soundtrack)</td>
</tr>
<tr>
<td>Phantom Of The Opera 2022</td>
<td>Valensia</td>
<td>Air Twister (Original Soundtrack)</td>
</tr>
<tr>
<td>Charming Welcome</td>
<td>Kevin Colombin</td>
<td>AKA (Original Soundtrack)</td>
</tr>
<tr>
<td>Tropical Whispering (Night)</td>
<td>Kevin Colombin</td>
<td>AKA (Original Soundtrack)</td>
</tr>
<tr>
<td>Anno Mutationem</td>
<td>Vanguard Sound</td>
<td>ANNO: Mutationem (Original Game Soundtrack) [Complete Edition]</td>
</tr>
<tr>
<td>Ephantom</td>
<td>Vanguard Sound</td>
<td>ANNO: Mutationem (Original Game Soundtrack) [Complete Edition]</td>
</tr>
<tr>
<td>8 Bit Almanac</td>
<td>Kieron Pepper</td>
<td>Arcade Paradise (Official Soundtrack)</td>
</tr>
<tr>
<td>Wipe Your Tears Away</td>
<td>Kieron Pepper &amp; Ben Pickersgill</td>
<td>Arcade Paradise (Official Soundtrack)</td>
</tr>
<tr>
<td>Shallow Breath</td>
<td>Matthew Barnes</td>
<td>As Dusk Falls (Original Soundtrack)</td>
</tr>
<tr>
<td>Assassin’s Creed Dawn of Ragnarök (Main Theme) [feat. Einar Selvik]</td>
<td>Stephanie Economou</td>
<td>Assassin's Creed Valhalla: Dawn of Ragnarök (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Stranger in a Strange Land</td>
<td>Stephanie Economou</td>
<td>Assassin's Creed Valhalla: Dawn of Ragnarök (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Golden Olive - Main Theme (feat. Asja Kadric)</td>
<td>Weifan Chang</td>
<td>Asterigos: Curse of the Stars (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Challenge the Thunder</td>
<td>Weifan Chang</td>
<td>Asterigos: Curse of the Stars (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Welcome to Beacon Pines</td>
<td>Matt Meyer</td>
<td>Beacon Pines (Original Soundtrack)</td>
</tr>
<tr>
<td>Heart to Heart</td>
<td>Matt Meyer</td>
<td>Beacon Pines (Original Soundtrack)</td>
</tr>
<tr>
<td>BLACKTAIL (feat. Zazula)</td>
<td>Arkadiusz Reikowski</td>
<td>BLACKTAIL (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Wiosna (feat. Bart Pałyga)</td>
<td>Arkadiusz Reikowski</td>
<td>BLACKTAIL (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Drawing Blood</td>
<td>Marskye</td>
<td>Boyfriend Dungeon: Secret Weapons (Original Game Soundtrack) - Single</td>
</tr>
<tr>
<td>Rainbow Six Siege European League Theme</td>
<td>The Toxic Avenger</td>
<td>BREACH (Rainbow Six European League Music) - EP</td>
</tr>
<tr>
<td>Modern Warfare II</td>
<td>Sarah Schachner</td>
<td>Call of Duty®: Modern Warfare II (Official Soundtrack)</td>
</tr>
<tr>
<td>Welcome to Las Almas</td>
<td>Sarah Schachner</td>
<td>Call of Duty®: Modern Warfare II (Official Soundtrack)</td>
</tr>
<tr>
<td>Prelude (feat. Stephen Gutman &amp; Thomas Gould)</td>
<td>Andrea Boccadoro</td>
<td>Card Shark (Original Soundtrack)</td>
</tr>
<tr>
<td>Ambush</td>
<td>Andrea Boccadoro</td>
<td>Card Shark (Original Soundtrack)</td>
</tr>
<tr>
<td>The Singer</td>
<td>Orkesta Mendoza</td>
<td>Cartel Tycoon (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Paraíso Apocalipsis</td>
<td>Chico Unicornio</td>
<td>Cartel Tycoon (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Main Theme of Chained Echoes</td>
<td>Eddie Marianukroh</td>
<td>Chained Echoes (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Dancing City of Farnsport</td>
<td>Eddie Marianukroh</td>
<td>Chained Echoes (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Never Forget Our Promise</td>
<td>Eddie Marianukroh</td>
<td>Chained Echoes (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Density</td>
<td>Amos Roddy</td>
<td>Citizen Sleeper (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Coalesce</td>
<td>Amos Roddy</td>
<td>Citizen Sleeper (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Possible Futures</td>
<td>Amos Roddy</td>
<td>Citizen Sleeper (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Only Road</td>
<td>Frosty River</td>
<td>Clayton - A Song of Greed (Cave Digger 2 Official Soundtrack)</td>
</tr>
<tr>
<td>The Valley Bleeds Gold</td>
<td>Mantella</td>
<td>Clayton - A Song of Greed (Cave Digger 2 Official Soundtrack)</td>
</tr>
<tr>
<td>Cobra Kai / Dojos Rising</td>
<td>Leo Birenberg, Zach Robinson, Dan Light &amp; Ramiro Rodriguez Zamarripa</td>
<td>Cobra Kai 2: Dojos Rising (Original Game Soundtrack)</td>
</tr>
<tr>
<td>[INSIGHT THEORY] -Home Screen: Sherlock-</td>
<td>CRIMESIGHT SOUND TEAM</td>
<td>CRIMESIGHT ORIGINAL SOUNDTRACK SELECTION</td>
</tr>
<tr>
<td>Praise the Lamb</td>
<td>River Boy</td>
<td>Cult of the Lamb (Original Soundtrack)</td>
</tr>
<tr>
<td>Start a Cult</td>
<td>River Boy</td>
<td>Cult of the Lamb (Original Soundtrack)</td>
</tr>
<tr>
<td>Unstoppable</td>
<td>Jason Smith</td>
<td>Cultic (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The World Asunder</td>
<td>Jason Smith</td>
<td>Cultic (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Delicious Last Course</td>
<td>Kristofer Maddigan</td>
<td>Cuphead - The Delicious Last Course (Original Soundtrack)</td>
</tr>
<tr>
<td>Snow Cult Scuffle</td>
<td>Kristofer Maddigan</td>
<td>Cuphead - The Delicious Last Course (Original Soundtrack)</td>
</tr>
<tr>
<td>A Fore-tuitous Meeting</td>
<td>Mark Sparling</td>
<td>Cursed To Golf (Original Soundtrack)</td>
</tr>
<tr>
<td>Rip</td>
<td>Mark Sparling</td>
<td>Cursed To Golf (Original Soundtrack)</td>
</tr>
<tr>
<td>Through the Brambles and Briar</td>
<td>Cris Velasco</td>
<td>Dauntless, Vol. 3 (Official Game Soundtrack)</td>
</tr>
<tr>
<td>Not Today, Satan</td>
<td>Chipzel</td>
<td>Dicey Dungeons: Reunion</td>
</tr>
<tr>
<td>No Thoughts, Only Dice</td>
<td>Chipzel</td>
<td>Dicey Dungeons: Reunion</td>
</tr>
<tr>
<td>In the City (feat. Anyar 暗鸦)</td>
<td>XHz Official</td>
<td>Dislyte - Grandis Vibes</td>
</tr>
<tr>
<td>Competition</td>
<td>Bae Hyuk</td>
<td>DNF Duel : Who's Next (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Rival</td>
<td>Bae Hyuk &amp; Lee Jaekwang</td>
<td>DNF Duel : Who's Next (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Towersworn Stronghold</td>
<td>Seltzki</td>
<td>Doko Roko (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Crow</td>
<td>Seltzki</td>
<td>Doko Roko (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Ancient Artifact</td>
<td>Cameron Paxton</td>
<td>Dome Keeper, Vol. 1: The Engineer (Original Soundtrack)</td>
</tr>
<tr>
<td>Delving</td>
<td>Cameron Paxton</td>
<td>Dome Keeper, Vol. 1: The Engineer (Original Soundtrack)</td>
</tr>
<tr>
<td>Ayla &amp; Falafel</td>
<td>Fearofdark</td>
<td>Dr Kobushi's Labyrinthine Laboratory (Original Soundtrack)</td>
</tr>
<tr>
<td>A Lab in Ruin</td>
<td>Fearofdark</td>
<td>Dr Kobushi's Labyrinthine Laboratory (Original Soundtrack)</td>
</tr>
<tr>
<td>Dwarf Fortress (Main Theme)</td>
<td>Dabu &amp; Simon Swerwer</td>
<td>Dwarf Fortress (Original Game Soundtrack)</td>
</tr>
<tr>
<td>First Year</td>
<td>Dabu</td>
<td>Dwarf Fortress (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Empowering Yourself (feat. London Contemporary Orchestra)</td>
<td>Olivier Deriviere</td>
<td>Dying Light 2 Stay Human (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Leyndell, Royal Capital</td>
<td>Yuka Kitamura</td>
<td>Elden Ring (Original Soundtrack)</td>
</tr>
<tr>
<td>Regal Ancestor Spirit</td>
<td>Yuka Kitamura</td>
<td>Elden Ring (Original Soundtrack)</td>
</tr>
<tr>
<td>Starscourge Radahn</td>
<td>Shoi Miyazawa</td>
<td>Elden Ring (Original Soundtrack)</td>
</tr>
<tr>
<td>Rennala, Queen of the Full Moon</td>
<td>Yuka Kitamura</td>
<td>Elden Ring (Original Soundtrack)</td>
</tr>
<tr>
<td>Elegy of the Sea (Original Game Soundtrack)</td>
<td>Sea of Thieves</td>
<td>Elegy of the Sea (Original Game Soundtrack) - Single</td>
</tr>
<tr>
<td>Circling</td>
<td>Joel Schoch</td>
<td>Far: Changing Tides</td>
</tr>
<tr>
<td>Travel</td>
<td>Joel Schoch</td>
<td>Far: Changing Tides</td>
</tr>
<tr>
<td>Rough Sea</td>
<td>Joel Schoch</td>
<td>Far: Changing Tides</td>
</tr>
<tr>
<td>Fired Up</td>
<td>XeroKat</td>
<td>Fired (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Test F1r37</td>
<td>XeroKat</td>
<td>Fired (Original Game Soundtrack)</td>
</tr>
<tr>
<td>First Snow</td>
<td>Corentin Brasart</td>
<td>Flat Eye (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>The Infinite Influx of Information</td>
<td>Corentin Brasart</td>
<td>Flat Eye (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Disco DNA</td>
<td>Fred Falke</td>
<td>Flippin Misfits (Original Game Soundtrack)</td>
</tr>
<tr>
<td>We've Got A Winner</td>
<td>Fred Falke</td>
<td>Flippin Misfits (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Next Day</td>
<td>Vile Monarch, Michał Dąbrowski &amp; Paweł Wawrzeńczyk</td>
<td>Floodland Part 1 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Together We Grow</td>
<td>Vile Monarch &amp; Piotr Musial</td>
<td>Floodland Part 1 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Little Earth (Day)</td>
<td>Mark Sparling</td>
<td>Flying Neko Delivery (Original Soundtrack)</td>
</tr>
<tr>
<td>Aether Canyon (Night)</td>
<td>Mark Sparling</td>
<td>Flying Neko Delivery (Original Soundtrack)</td>
</tr>
<tr>
<td>Welcome to Foretales</td>
<td>Christophe Héral</td>
<td>Foretales (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Land of the Gods (feat. Raphaël Joffres)</td>
<td>Christophe Héral</td>
<td>Foretales (Original Game Soundtrack)</td>
</tr>
<tr>
<td>A Quiet Spring Day (Side A)</td>
<td>Josie Brechner</td>
<td>Freshly Frosted a-Sides (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Cozy Autumn (Side A)</td>
<td>Josie Brechner</td>
<td>Freshly Frosted a-Sides (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Floating World Under the Moonlight</td>
<td>HOYO-MiX</td>
<td>Genshin Impact - Footprints of the Traveler (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Hustle and Bustle of Ormos</td>
<td>HOYO-MiX</td>
<td>Genshin Impact - Forest of Jnana and Vidya (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Hope or Nostalgia</td>
<td>HOYO-MiX</td>
<td>Genshin Impact - Islands of the Lost and Forgotten (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Under the Sun</td>
<td>HOYO-MiX</td>
<td>Genshin Impact - The Shimmering Voyage, Vol. 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Ghost Song</td>
<td>Grant Graham</td>
<td>Ghost Song (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Big Bot</td>
<td>Grant Graham</td>
<td>Ghost Song (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Ghostwire:Tokyo</td>
<td>Masatoshi Yanagi</td>
<td>Ghostwire Tokyo (Original Game Soundtrack)</td>
</tr>
<tr>
<td>New Dawn</td>
<td>scntfc</td>
<td>Gibbon: Beyond the Trees</td>
</tr>
<tr>
<td>Liberation</td>
<td>scntfc</td>
<td>Gibbon: Beyond the Trees</td>
</tr>
<tr>
<td>God of War Ragnarök (feat. Eivør)</td>
<td>Bear McCreary</td>
<td>God of War Ragnarök (Original Soundtrack)</td>
</tr>
<tr>
<td>A Son's Path</td>
<td>Bear McCreary</td>
<td>God of War Ragnarök (Original Soundtrack)</td>
</tr>
<tr>
<td>The All-Father</td>
<td>Bear McCreary</td>
<td>God of War Ragnarök (Original Soundtrack)</td>
</tr>
<tr>
<td>Gotham Knights - Main Title Theme</td>
<td>The Flight &amp; Gotham Knights</td>
<td>Gotham Knights (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Harley Quinn - Light the Candles</td>
<td>The Flight &amp; Gotham Knights</td>
<td>Gotham Knights (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Upper Grasslands Night</td>
<td>Finishing Move Inc.</td>
<td>Grounded - Year 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Fight of the Orchid</td>
<td>Finishing Move Inc.</td>
<td>Grounded - Year 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>End of Dragons (feat. Maclaine Diemer)</td>
<td>Arenanet</td>
<td>Guild Wars 2: End of Dragons Original Soundtrack Part I</td>
</tr>
<tr>
<td>Along the Shing Jea Shoreline (feat. Maclaine Diemer)</td>
<td>Arenanet</td>
<td>Guild Wars 2: End of Dragons Original Soundtrack Part I</td>
</tr>
<tr>
<td>Highway Chaos (STAGE1)</td>
<td>Exemia</td>
<td>GUNVEIN (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Desert Destruction (STAGE 2)</td>
<td>Exemia</td>
<td>GUNVEIN (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Das Spligen</td>
<td>Michele Postpischl</td>
<td>Hell Is Others (Original Videogame Soundtrack)</td>
</tr>
<tr>
<td>The Awaiting</td>
<td>Michele Postpischl</td>
<td>Hell Is Others (Original Videogame Soundtrack)</td>
</tr>
<tr>
<td>HEROish Main Theme</td>
<td>Sunblink &amp; Phill Boucher</td>
<td>HEROish (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Cursed Lands</td>
<td>Sunblink &amp; Phill Boucher</td>
<td>HEROish (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Whatever Comes (feat. Julie Elven &amp; Melissa R. Kaplan)</td>
<td>Oleksa Lozowchuk</td>
<td>Horizon Forbidden West (Original Soundtrack)</td>
</tr>
<tr>
<td>Aloy's Theme - Forbidden West (feat. Julie Elven)</td>
<td>Joris de Man</td>
<td>Horizon Forbidden West (Original Soundtrack)</td>
</tr>
<tr>
<td>Exocolonist Theme Redux</td>
<td>A Shell In The Pit</td>
<td>I Was a Teenage Exocolonist (Original Soundtrack)</td>
</tr>
<tr>
<td>Quiet</td>
<td>C418</td>
<td>I Was a Teenage Exocolonist (Original Soundtrack)</td>
</tr>
<tr>
<td>Immortality - Opening</td>
<td>Nainita Desai</td>
<td>Immortality (Original Soundtrack to the Interactive Trilogy)</td>
</tr>
<tr>
<td>Religion</td>
<td>Nainita Desai</td>
<td>Immortality (Original Soundtrack to the Interactive Trilogy)</td>
</tr>
<tr>
<td>Overture</td>
<td>Jose Varon</td>
<td>Imp of the Sun (Original Soundtrack)</td>
</tr>
<tr>
<td>Mountains Ruins</td>
<td>Jose Varon</td>
<td>Imp of the Sun (Original Soundtrack)</td>
</tr>
<tr>
<td>Ensorcelled by Bloodshed</td>
<td>Catton Arthur</td>
<td>Impaler (Original Soundtrack) - EP</td>
</tr>
<tr>
<td>Shooting Star Overdrive</td>
<td>Turbo</td>
<td>Inertial Drift: Twilight Rivals Edition Eurobeat Soundtrack</td>
</tr>
<tr>
<td>ITORAH Main Title</td>
<td>Filippo Beck Peccoz</td>
<td>ITORAH a-Side (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Nahu Fields</td>
<td>Filippo Beck Peccoz</td>
<td>ITORAH a-Side (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Vanir’s Legacy</td>
<td>Guillaume David</td>
<td>IXION (Original Soundtrack)</td>
</tr>
<tr>
<td>No Turning Back</td>
<td>Guillaume David</td>
<td>IXION (Original Soundtrack)</td>
</tr>
<tr>
<td>Sakura Hills</td>
<td>Sebastien Romero</td>
<td>Jitsu Squad (Original Soundtrack)</td>
</tr>
<tr>
<td>Castle Hellstorm</td>
<td>Sebastien Romero</td>
<td>Jitsu Squad (Original Soundtrack)</td>
</tr>
<tr>
<td>K'Sante, The Pride of Nazumah (Champion Theme)</td>
<td>League of Legends</td>
<td>K'Sante, The Pride of Nazumah (Champion Theme) - Single</td>
</tr>
<tr>
<td>The Neon Riverside</td>
<td>Magic: The Gathering, Zac Zinger &amp; insaneintherainmusic</td>
<td>Kamigawa: Neon Dynasty (Official Soundtrack)</td>
</tr>
<tr>
<td>Beneath Worlds</td>
<td>Theophany</td>
<td>Kena: Bridge of Spirits, Vol. 1 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Abandoned Village</td>
<td>Theophany</td>
<td>Kena: Bridge of Spirits, Vol. 1 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Fictitious or Real (Team Sacred Treasures)</td>
<td>SNK SOUND TEAM</td>
<td>THE KING OF FIGHTERS XV (Original Sound Track)</td>
</tr>
<tr>
<td>Ride the Big Wave! (Team Fatal Fury)</td>
<td>SNK SOUND TEAM</td>
<td>THE KING OF FIGHTERS XV (Original Sound Track)</td>
</tr>
<tr>
<td>Eikthyrnir (Extended)</td>
<td>Kalandra</td>
<td>Kingdom Two Crowns: Norse Lands Soundtrack (Extended)</td>
</tr>
<tr>
<td>Helheim (Extended)</td>
<td>Kalandra</td>
<td>Kingdom Two Crowns: Norse Lands Soundtrack (Extended)</td>
</tr>
<tr>
<td>Welcome Back, Kirby</td>
<td>Hirokazu Ando</td>
<td>Kirby and the Forgotten Land Original Soundtrack</td>
</tr>
<tr>
<td>The Battle of Blizzard Bridge</td>
<td>Hirokazu Ando</td>
<td>Kirby and the Forgotten Land Original Soundtrack</td>
</tr>
<tr>
<td>Forgo's Treasures</td>
<td>Hirokazu Ando</td>
<td>Kirby and the Forgotten Land Original Soundtrack</td>
</tr>
<tr>
<td>The Battle of the Broken Sky</td>
<td>Damian Sanchez</td>
<td>The Knight Witch (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Forge Fields</td>
<td>Damian Sanchez</td>
<td>The Knight Witch (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Festival Grounds</td>
<td>TiceTunes</td>
<td>Kynseed (Music from the Early Access version of the game)</td>
</tr>
<tr>
<td>Mosswhisper Ruins</td>
<td>TiceTunes</td>
<td>Kynseed (Music from the Early Access version of the game)</td>
</tr>
<tr>
<td>Sawayama Solitaire</td>
<td>Matthew S Burns</td>
<td>Last Call BBS (Original Soundtrack)</td>
</tr>
<tr>
<td>Cursed Knowledge</td>
<td>Matthew S Burns</td>
<td>Last Call BBS (Original Soundtrack)</td>
</tr>
<tr>
<td>The Last Clockwinder</td>
<td>Joel Corelitz</td>
<td>The Last Clockwinder (Original Soundtrack)</td>
</tr>
<tr>
<td>Gardeners: Luftapples</td>
<td>Joel Corelitz</td>
<td>The Last Clockwinder (Original Soundtrack)</td>
</tr>
<tr>
<td>In the Dawn</td>
<td>Falcom Sound Team jdk</td>
<td>The Legend of Heroes: Kuro No Kiseki Original Soundtrack</td>
</tr>
<tr>
<td>Wild Beat</td>
<td>Falcom Sound Team jdk</td>
<td>The Legend of Heroes: Kuro No Kiseki Original Soundtrack</td>
</tr>
<tr>
<td>Resonance of Ray</td>
<td>Falcom Sound Team jdk</td>
<td>The Legend of Heroes: Kuro No Kiseki Original Soundtrack</td>
</tr>
<tr>
<td>Lord Winklebottom Investigates</td>
<td>Gustavo Coutinho</td>
<td>Lord Winklebottom Investigates (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Looking for Clues</td>
<td>Gustavo Coutinho</td>
<td>Lord Winklebottom Investigates (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Lambs in the Meadow</td>
<td>Adam Bow</td>
<td>Lords and Villeins (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Yellow Trees</td>
<td>Adam Bow</td>
<td>Lords and Villeins (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Lunistice</td>
<td>Knasibas</td>
<td>Lunistice (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Forest (Melancholy)</td>
<td>Knasibas</td>
<td>Lunistice (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Yearning to Bloom</td>
<td>Yoko Shimomura</td>
<td>Mario + Rabbids Sparks of Hope (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Fuzzy and Fleeting</td>
<td>Yoko Shimomura</td>
<td>Mario + Rabbids Sparks of Hope (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Desolate Beauty</td>
<td>Gareth Coker</td>
<td>Mario + Rabbids Sparks of Hope (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Bizarre Bazaar</td>
<td>Richard Jacques</td>
<td>Marvel's Guardians of the Galaxy: Welcome to Knowhere (Original Video Game Soundtrack) - EP</td>
</tr>
<tr>
<td>Origins</td>
<td>Tim Wynn</td>
<td>Marvel's Midnight Suns (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Discordance</td>
<td>Danimal Cannon</td>
<td>Massive - Single</td>
</tr>
<tr>
<td>Shopping</td>
<td>Half Asleep Games &amp; Filippo Vicarelli</td>
<td>Melatonin (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Past</td>
<td>Half Asleep Games</td>
<td>Melatonin (Original Game Soundtrack)</td>
</tr>
<tr>
<td>I'm There Too</td>
<td>Joel Corelitz, e.hillman &amp; Imogen</td>
<td>A Memoir Blue (Original Soundtrack)</td>
</tr>
<tr>
<td>Hands Held in Hearts (Reprise)</td>
<td>Joel Corelitz, e.hillman &amp; Imogen</td>
<td>A Memoir Blue (Original Soundtrack)</td>
</tr>
<tr>
<td>Red Eyes</td>
<td>Noisecream</td>
<td>Midnight Fight Express (Original Game Soundtrack), Pt. 1</td>
</tr>
<tr>
<td>Shadow of Light</td>
<td>Noisecream</td>
<td>Midnight Fight Express (Original Game Soundtrack), Pt. 1</td>
</tr>
<tr>
<td>Aerie</td>
<td>Lena Raine</td>
<td>Minecraft: The Wild Update (Original Game Soundtrack) - EP</td>
</tr>
<tr>
<td>We Remember You</td>
<td>Jason Graves</td>
<td>Moss: Book II (Original Game Soundtrack)</td>
</tr>
<tr>
<td>When One Door Shuts</td>
<td>Jason Graves</td>
<td>Moss: Book II (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Daydream</td>
<td>Stephen Barton &amp; MultiVersus</td>
<td>MultiVersus (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Get Ready to Fight! (Character Select)</td>
<td>Gordy Haab &amp; MultiVersus</td>
<td>MultiVersus (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Head in the Clouds</td>
<td>Charles Bardin &amp; Valentin Ducloux</td>
<td>A Musical Story</td>
</tr>
<tr>
<td>Love at First Sight</td>
<td>Charles Bardin &amp; Valentin Ducloux</td>
<td>A Musical Story</td>
</tr>
<tr>
<td>Jupiter</td>
<td>Brodinski</td>
<td>Need for Speed: Unbound (Original Soundtrack)</td>
</tr>
<tr>
<td>Cybercrime</td>
<td>Brodinski</td>
<td>Need for Speed: Unbound (Original Soundtrack)</td>
</tr>
<tr>
<td>Glass Ocean</td>
<td>Machine Girl</td>
<td>Neon White Soundtrack, Pt. 1 (the Wicked Heart)</td>
</tr>
<tr>
<td>House of Cards</td>
<td>Machine Girl</td>
<td>Neon White Soundtrack, Pt. 1 (the Wicked Heart)</td>
</tr>
<tr>
<td>Lucid Mayhem</td>
<td>Andrew Hulshult</td>
<td>Nightmare Reaper (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Nilah, the Joy Unbound (Champion Theme)</td>
<td>League of Legends</td>
<td>Nilah, the Joy Unbound - Single</td>
</tr>
<tr>
<td>Closed Circuit Television</td>
<td>LudoWic</td>
<td>Nitro Kid (Original Soundtrack, LudoWic) - EP</td>
</tr>
<tr>
<td>Legendary Dungeons</td>
<td>Jim Guthrie</td>
<td>Nobody Saves the World (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Road to the Shards</td>
<td>Jim Guthrie</td>
<td>Nobody Saves the World (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Chex2Cash</td>
<td>Andy G</td>
<td>NORCO Original Soundtrack</td>
</tr>
<tr>
<td>Head in the Lake</td>
<td>Gewgawly I</td>
<td>NORCO Original Soundtrack</td>
</tr>
<tr>
<td>New Here &amp; Feeling at Home</td>
<td>Jennifur</td>
<td>Nowhere, Now Here (music made for the game Shredders)</td>
</tr>
<tr>
<td>To the One I Used to be</td>
<td>Jennifur</td>
<td>Nowhere, Now Here (music made for the game Shredders)</td>
</tr>
<tr>
<td>Nightfall</td>
<td>Thomas J. Peters</td>
<td>Numina (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Alyuin ~the Return Home~</td>
<td>Thomas J. Peters</td>
<td>Numina (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Rule the World (feat. Budapest Film Orchestra)</td>
<td>Håkan Glänte</td>
<td>Original Soundtrack of Victoria 3</td>
</tr>
<tr>
<td>British Soil (feat. Budapest Film Orchestra)</td>
<td>Håkan Glänte</td>
<td>Original Soundtrack of Victoria 3</td>
</tr>
<tr>
<td>Wild Whispering Forest</td>
<td>Kimmo Savilampi</td>
<td>The Outbound Ghost (The Original Soundtrack)</td>
</tr>
<tr>
<td>In the Horizon (feat. Gregory Orosz)</td>
<td>Kimmo Savilampi</td>
<td>The Outbound Ghost (The Original Soundtrack)</td>
</tr>
<tr>
<td>Level 1 (2 Dimensions)</td>
<td>Victor Butzelaar</td>
<td>The Past Within (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Patrick's Parabox</td>
<td>Priscilla Snow</td>
<td>Patrick's Parabox (Original Soundtrack)</td>
</tr>
<tr>
<td>Transfer</td>
<td>Priscilla Snow</td>
<td>Patrick's Parabox (Original Soundtrack)</td>
</tr>
<tr>
<td>The High Courts</td>
<td>Adam Al-Sawad</td>
<td>The Pegasus Expedition (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Await the Stars</td>
<td>Adam Al-Sawad</td>
<td>The Pegasus Expedition (Original Game Soundtrack)</td>
</tr>
<tr>
<td>City of Reason</td>
<td>Alkemie</td>
<td>Pentiment (Original Soundtrack)</td>
</tr>
<tr>
<td>Out of the Shadows</td>
<td>Alkemie</td>
<td>Pentiment (Original Soundtrack)</td>
</tr>
<tr>
<td>Perfect Tides</td>
<td>Daniel Kobylarz</td>
<td>Perfect Tides (Original Game Soundtrack)</td>
</tr>
<tr>
<td>New Reunion</td>
<td>Daniel Kobylarz</td>
<td>Perfect Tides (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Pitstop in Purgatory</td>
<td>Tymedust</td>
<td>Pitstop in Purgatory (Original Game Soundtrack)</td>
</tr>
<tr>
<td>My Face in the Sand</td>
<td>Tymedust</td>
<td>Pitstop in Purgatory (Original Game Soundtrack)</td>
</tr>
<tr>
<td>A Plague Tale Requiem (feat. Estonian Philharmonic Chamber Choir &amp; Eric-Maria Couturier)</td>
<td>Olivier Deriviere</td>
<td>A Plague Tale: Requiem (Original Soundtrack)</td>
</tr>
<tr>
<td>The Dream (feat. Estonian Philharmonic Chamber Choir &amp; Eric-Maria Couturier)</td>
<td>Olivier Deriviere</td>
<td>A Plague Tale: Requiem (Original Soundtrack)</td>
</tr>
<tr>
<td>The Friendly Lucas (feat. Giani Caserotto)</td>
<td>Olivier Deriviere</td>
<td>A Plague Tale: Requiem (Original Soundtrack)</td>
</tr>
<tr>
<td>Lobby</td>
<td>Calum Bowen</td>
<td>Poinpy (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Bubbly Waterway</td>
<td>Calum Bowen</td>
<td>Poinpy (Original Game Soundtrack)</td>
</tr>
<tr>
<td>East Province (Walking)</td>
<td>GAME FREAK</td>
<td>Pokémon Scarlet &amp; Pokémon Violet</td>
</tr>
<tr>
<td>Battle! (Tera Raid)</td>
<td>GAME FREAK</td>
<td>Pokémon Scarlet &amp; Pokémon Violet</td>
</tr>
<tr>
<td>Final Battle! (Nemona)</td>
<td>GAME FREAK</td>
<td>Pokémon Scarlet &amp; Pokémon Violet</td>
</tr>
<tr>
<td>Area Zero</td>
<td>GAME FREAK</td>
<td>Pokémon Scarlet &amp; Pokémon Violet</td>
</tr>
<tr>
<td>Roxanne's Invitation (feat. Patti Rudisill)</td>
<td>Greg Nicolett</td>
<td>Potionomics (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Muktuk's Calling</td>
<td>Greg Nicolett</td>
<td>Potionomics (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Cripsy</td>
<td>ThorHighHeels</td>
<td>Producer 2021 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Quarry Main Theme</td>
<td>Ian Livingstone</td>
<td>The Quarry (Original Soundtrack)</td>
</tr>
<tr>
<td>Beautiful Evening</td>
<td>Ian Livingstone</td>
<td>The Quarry (Original Soundtrack)</td>
</tr>
<tr>
<td>Assault on Valor</td>
<td>Chase Bethea</td>
<td>Questlike: Pocket Original Soundtrack - EP</td>
</tr>
<tr>
<td>Stage Y3.0: Geirrod Fortress Strategic War</td>
<td>Granzella</td>
<td>R-Type Final 2 Homage Stage Soundtrack, Vol. 2</td>
</tr>
<tr>
<td>Stage Y6.0: Pluto Outer Base Glitnir Strategic War</td>
<td>Granzella</td>
<td>R-Type Final 2 Homage Stage Soundtrack, Vol. 2</td>
</tr>
<tr>
<td>Unsung Hero</td>
<td>Alberto Martino</td>
<td>Reaper's March (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Guardians of the Forest</td>
<td>Alberto Martino</td>
<td>Reaper's March (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Depths of Hell</td>
<td>Zardonic</td>
<td>Redout 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Gaijin's Prooving</td>
<td>Leonardo Mazzella</td>
<td>Redout 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Once in a Lullaby (feat. Kalliopi Mitropoulou &amp; Christos Tsogias-Razakov)</td>
<td>Chris Christodoulou</td>
<td>Risk of Rain 2: Survivors of the Void</td>
</tr>
<tr>
<td>They Might as Well Be Dead</td>
<td>Chris Christodoulou</td>
<td>Risk of Rain 2: Survivors of the Void</td>
</tr>
<tr>
<td>Throw Some Shade</td>
<td>Megan McDuffee</td>
<td>River City Girls 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Feel the Heat (feat. Matthew Lister)</td>
<td>Megan McDuffee</td>
<td>River City Girls 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Roadwarden</td>
<td>Nick Roder</td>
<td>Roadwarden</td>
</tr>
<tr>
<td>Citadel Agartha (feat. Gordon Mcgladdery)</td>
<td>Tettix</td>
<td>Rogue Legacy 2 (Original Soundtrack)</td>
</tr>
<tr>
<td>Shifting Halls</td>
<td>A Shell In The Pit</td>
<td>Rogue Legacy 2 (Original Soundtrack)</td>
</tr>
<tr>
<td>Echo Basin</td>
<td>Electric Dragon</td>
<td>Rollerdrome (Original Soundtrack)</td>
</tr>
<tr>
<td>To the Death</td>
<td>Electric Dragon</td>
<td>Rollerdrome (Original Soundtrack)</td>
</tr>
<tr>
<td>Fast As You Can</td>
<td>Thomas Barrandon</td>
<td>RUN : The world in-between (Original Soundtrack)</td>
</tr>
<tr>
<td>Memories</td>
<td>Thomas Barrandon</td>
<td>RUN : The world in-between (Original Soundtrack)</td>
</tr>
<tr>
<td>Here for a Bad Time</td>
<td>ZaKKu</td>
<td>Rungore: Beginner Experience Original Soundtrack</td>
</tr>
<tr>
<td>The Citadel</td>
<td>Fat Bard</td>
<td>Runner (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Daedalus</td>
<td>Fat Bard</td>
<td>Runner (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Saints Row (Main Theme)</td>
<td>Malcolm Kirby Jr.</td>
<td>Saints Row (Main Theme) - Single</td>
</tr>
<tr>
<td>For Every Three, One to the Gods</td>
<td>Maclaine Diemer</td>
<td>Salt and Sacrifice (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Elder Copse</td>
<td>Maclaine Diemer</td>
<td>Salt and Sacrifice (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>Flink</td>
<td>Moonsailor</td>
<td>SCHiM - EP</td>
</tr>
<tr>
<td>The Last Sunset</td>
<td>Punch Deck</td>
<td>Sector's Edge: Volume II (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Prepare to Engage</td>
<td>Punch Deck</td>
<td>Sector's Edge: Volume II (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Pulse of Sephonie Island</td>
<td>Melos Han-Tani</td>
<td>Sephonie (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Canopy of Verdant and Sandstones</td>
<td>Melos Han-Tani</td>
<td>Sephonie (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Strange Land</td>
<td>Light Return</td>
<td>The Serpent Rogue (Original Soundtrack)</td>
</tr>
<tr>
<td>The Warden</td>
<td>Light Return</td>
<td>The Serpent Rogue (Original Soundtrack)</td>
</tr>
<tr>
<td>Works and Ways Unseen</td>
<td>Ryan Ike</td>
<td>Shadows over Loathing (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Terrence's Theme</td>
<td>Ryan Ike</td>
<td>Shadows over Loathing (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Great Lighthouse</td>
<td>Philippe Grant</td>
<td>Ship of Fools (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Hail's Abode</td>
<td>Philippe Grant</td>
<td>Ship of Fools (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Overground (Main)</td>
<td>Jake Kaufman</td>
<td>Shovel Knight Dig (Original Soundtrack)</td>
</tr>
<tr>
<td>Thermal Vent (Secret Fountain)</td>
<td>Jake Kaufman</td>
<td>Shovel Knight Dig (Original Soundtrack)</td>
</tr>
<tr>
<td>Who Needs Honor (Drill Knight's Castle)</td>
<td>Jake Kaufman</td>
<td>Shovel Knight Dig (Original Soundtrack)</td>
</tr>
<tr>
<td>Lava Land</td>
<td>Sam Webster</td>
<td>Skies of Chaos (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Skyline (feat. Nick Nausbaum)</td>
<td>Sam Webster</td>
<td>Skies of Chaos (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Dragon Dance</td>
<td>Vincent Diamante</td>
<td>Sky (Original Game Soundtrack) Vol. 4</td>
</tr>
<tr>
<td>Gentle Flight</td>
<td>Vincent Diamante</td>
<td>Sky (Original Game Soundtrack) Vol. 4</td>
</tr>
<tr>
<td>The Conservatory (Day)</td>
<td>Harry Mack &amp; Monomi Park</td>
<td>Slime Rancher 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Starlight Strand (Relaxed)</td>
<td>Harry Mack &amp; Monomi Park</td>
<td>Slime Rancher 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Radio (Mossy Beat)</td>
<td>Harry Mack &amp; Monomi Park</td>
<td>Slime Rancher 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Sliders On the Storm</td>
<td>Snail Trail Inc.</td>
<td>Snail Trail, Vol. 2 (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Song of TRIANGLE STRATEGY (feat. SARINA, Reina &amp; MARU)</td>
<td>Akira Senju</td>
<td>Song of TRIANGLE STRATEGY (feat. SARINA, REINA &amp; MARU) - Single</td>
</tr>
<tr>
<td>I’m Here (feat. Merry Kirk-Holmes)</td>
<td>SEGA / Tomoya Ohtani</td>
<td>Sonic Frontiers (Original Soundtrack Stillness &amp; Motion)</td>
</tr>
<tr>
<td>Cyber Space 1-2: Flowing</td>
<td>SEGA / Tomoya Ohtani</td>
<td>Sonic Frontiers (Original Soundtrack Stillness &amp; Motion)</td>
</tr>
<tr>
<td>Cyber Space 1-6: Go Back 2 Your Roots</td>
<td>SEGA / Kenji Mizuno</td>
<td>Sonic Frontiers (Original Soundtrack Stillness &amp; Motion)</td>
</tr>
<tr>
<td>Ares Island: 5th Mvt.</td>
<td>SEGA / Tomoya Ohtani</td>
<td>Sonic Frontiers (Original Soundtrack Stillness &amp; Motion)</td>
</tr>
<tr>
<td>The Pride of Zarga</td>
<td>Will Savino</td>
<td>Souldiers (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Forest of Fyr</td>
<td>Will Savino</td>
<td>Souldiers (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Starthropod</td>
<td>Professor Kliq</td>
<td>SpiderHeck (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Dead City</td>
<td>Yann Van Der Cruyssen</td>
<td>Stray (Original Soundtrack)</td>
</tr>
<tr>
<td>The Notebooks</td>
<td>Yann Van Der Cruyssen</td>
<td>Stray (Original Soundtrack)</td>
</tr>
<tr>
<td>Strongholds of the Sea (Original Game Soundtrack)</td>
<td>Sea of Thieves</td>
<td>Strongholds of the Sea (Original Game Soundtrack) - Single</td>
</tr>
<tr>
<td>Super Bullet Break</td>
<td>Fumitaka Amemiya</td>
<td>Super Bullet Break (Original Soundtrack)</td>
</tr>
<tr>
<td>Memories of Home</td>
<td>Fumitaka Amemiya</td>
<td>Super Bullet Break (Original Soundtrack)</td>
</tr>
<tr>
<td>The Hymn Of Vaghen</td>
<td>Inon Zur</td>
<td>Syberia: The World Before (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Kate Walker (The World Before Variation)</td>
<td>Inon Zur</td>
<td>Syberia: The World Before (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Jaw-Breaking News!</td>
<td>Tee Lopes</td>
<td>Teenage Mutant Ninja Turtles: Shredder's Revenge (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Panic in the Sky!</td>
<td>Tee Lopes &amp; Jonny Atma</td>
<td>Teenage Mutant Ninja Turtles: Shredder's Revenge (Original Game Soundtrack)</td>
</tr>
<tr>
<td>We Ain't Came to Lose</td>
<td>Raekwon &amp; Ghostface Killah</td>
<td>Teenage Mutant Ninja Turtles: Shredder's Revenge (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Comet</td>
<td>yu-t</td>
<td>Temp Zero (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Transient</td>
<td>yu-t</td>
<td>Temp Zero (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Tiny Tina’s Wonderlands Theme</td>
<td>Joshua Carro</td>
<td>Tiny Tina's Wonderlands (Original Soundtrack)</td>
</tr>
<tr>
<td>Enter the Weepwild Dankness</td>
<td>Joshua Carro</td>
<td>Tiny Tina's Wonderlands (Original Soundtrack)</td>
</tr>
<tr>
<td>Transidor Crossing</td>
<td>Alexis Laugier</td>
<td>Tinykin (Original Soundtrack)</td>
</tr>
<tr>
<td>Into the Pipe</td>
<td>Alexis Laugier</td>
<td>Tinykin (Original Soundtrack)</td>
</tr>
<tr>
<td>Countdown Theme</td>
<td>Sascha Dikiciyan</td>
<td>Tom Clancy's The Division 2: Countdown (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Sakura Fubuki</td>
<td>Yoko Honda</td>
<td>Trek to Yomi (Original Soundtrack)</td>
</tr>
<tr>
<td>Bodies Blowing in the Wind</td>
<td>Cody Matthew Johnson</td>
<td>Trek to Yomi (Original Soundtrack)</td>
</tr>
<tr>
<td>The Weight of Rain</td>
<td>Lifeformed &amp; Janice Kwan</td>
<td>TUNIC (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Redwood Colonnade</td>
<td>Lifeformed &amp; Janice Kwan</td>
<td>TUNIC (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Ooze Control</td>
<td>Lifeformed &amp; Janice Kwan</td>
<td>TUNIC (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Overture (Main Theme)</td>
<td>Matthijs Dierckx</td>
<td>Unexplored 2 (The Best of the Original Soundtrack)</td>
</tr>
<tr>
<td>Foresting</td>
<td>Matthijs Dierckx</td>
<td>Unexplored 2 (The Best of the Original Soundtrack)</td>
</tr>
<tr>
<td>Porchstep (feat. King Killjoy, Austin M. &amp; Huey Leone)</td>
<td>ValiDate: Struggling Soundteams In Your Area</td>
<td>ValiDate, Vol. 1 (Original Game Soundtrack) [feat. King Killjoy, Austin M. &amp; Huey Leone]</td>
</tr>
<tr>
<td>Layabout (feat. King Killjoy, Austin M. &amp; Huey Leone)</td>
<td>ValiDate: Struggling Soundteams In Your Area</td>
<td>ValiDate, Vol. 1 (Original Game Soundtrack) [feat. King Killjoy, Austin M. &amp; Huey Leone]</td>
</tr>
<tr>
<td>Broken Mind</td>
<td>Olivier Deriviere</td>
<td>Vampire: The Masquerade – Swansong (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Play with a Prey</td>
<td>Olivier Deriviere</td>
<td>Vampire: The Masquerade – Swansong (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Welcome To The Arena</td>
<td>Petr Knedlík</td>
<td>Vectro Blast: vol II (Original Game Soundtrack) - EP</td>
</tr>
<tr>
<td>Chamber Militant</td>
<td>Doyle W. Donehoo</td>
<td>Warhammer 40,000: Chaos Gate - Daemonhunters</td>
</tr>
<tr>
<td>The Reapers of the Bloom</td>
<td>Doyle W. Donehoo</td>
<td>Warhammer 40,000: Chaos Gate - Daemonhunters</td>
</tr>
<tr>
<td>Darktide Main Theme</td>
<td>Jesper Kyd</td>
<td>Warhammer 40,000: Darktide (Original Soundtrack)</td>
</tr>
<tr>
<td>Disposal Unit (Imperium Mix)</td>
<td>Jesper Kyd</td>
<td>Warhammer 40,000: Darktide (Original Soundtrack)</td>
</tr>
<tr>
<td>Enter the Night Orchard</td>
<td>Joel Bille</td>
<td>WaveTale (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Journey To Candle Hill</td>
<td>Joel Bille</td>
<td>WaveTale (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Fool's Gold</td>
<td>OFK</td>
<td>We Are OFK - EP</td>
</tr>
<tr>
<td>Forever</td>
<td>Total Mayhem Games</td>
<td>We Were Here Forever (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Farewell</td>
<td>Total Mayhem Games</td>
<td>We Were Here Forever (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Ghost Voices</td>
<td>Weird Wolves, Ava Gore &amp; Raphael Colantonio</td>
<td>Weird West (Original Soundtrack)</td>
</tr>
<tr>
<td>Overdrive (Weird Mix)</td>
<td>Weird Wolves</td>
<td>Weird West (Original Soundtrack)</td>
</tr>
<tr>
<td>Summit Swing</td>
<td>James Renna</td>
<td>Wizardchess - Single</td>
</tr>
<tr>
<td>Rain City</td>
<td>Isadora Penna</td>
<td>Wolfstride (Original Game Soundtrack)</td>
</tr>
<tr>
<td>Battle Theme</td>
<td>Isadora Penna</td>
<td>Wolfstride (Original Game Soundtrack)</td>
</tr>
<tr>
<td>The Dragon's Hoard</td>
<td>David Arkenstone &amp; Jake Lefkowitz</td>
<td>World of Warcraft: Dragonflight</td>
</tr>
<tr>
<td>Keves Colony</td>
<td>Yasunori Mitsuda, ACE (TOMOri Kudo, CHiCO), Kenji Hiramatsu, Manami Kiyota, Mariam Abounnasr</td>
<td>Xenoblade Chronicles 3</td>
</tr>
<tr>
<td>Erythia Sea</td>
<td>Yasunori Mitsuda, ACE (TOMOri Kudo, CHiCO), Kenji Hiramatsu, Manami Kiyota, Mariam Abounnasr</td>
<td>Xenoblade Chronicles 3</td>
</tr>
<tr>
<td>Keves Battle</td>
<td>Yasunori Mitsuda, ACE (TOMOri Kudo, CHiCO), Kenji Hiramatsu, Manami Kiyota, Mariam Abounnasr</td>
<td>Xenoblade Chronicles 3</td>
</tr>
<tr>
<td>The Weight of Life</td>
<td>Yasunori Mitsuda, ACE (TOMOri Kudo, CHiCO), Kenji Hiramatsu, Manami Kiyota, Mariam Abounnasr</td>
<td>Xenoblade Chronicles 3</td>
</tr>
<tr>
<td>Fast and Frosty</td>
<td>Muuutsch</td>
<td>You Suck At Parking (Original Video Game Soundtrack)</td>
</tr>
<tr>
<td>果てなく続く紺碧の先を眺めて</td>
<td>堀諭史</td>
<td>モンスターハンターライズ:サンブレイク オリジナル・サウンドトラック</td>
</tr>
<tr>
<td>終わりなき迷路/樹海:Sunbreak ver.</td>
<td>鈴木まり香</td>
<td>モンスターハンターライズ:サンブレイク オリジナル・サウンドトラック</td>
</tr>
</tbody>
</table>
</div>
</details>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>2022 in review</title>
    <link href="https://geoffrich.net/posts/2022/"/>
    <updated>2023-01-08T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/2022/</id>
    
    <content type="html"><![CDATA[
      <p>It's my first time doing one of these, but I wanted to take a little time to look back at the year that was.</p>
<h2>Writing</h2>
<p>I wrote 12 articles this year (excluding posts that were linking to other content), which feels good to me. I'd always like to write more, but I have more ideas than time and an average of 1 a month feels like a good pace. My favorite posts were the ones where I did a deep dive on integrating Svelte with &quot;something else&quot;—the experimental <a href="/posts/page-transitions-1/">view transition API</a>, 11ty's framework-agnostic <a href="/posts/sveltekit-is-land/">partial hydration solution</a>, and Vercel's Satori library for <a href="/posts/svelte-social-image/">custom og:images</a>. For me, writing about something makes it real—if nothing else, so I can look back at my article titles from the last year and remember what I did.</p>
<p>My most popular post from this year was on Svelte's new <a href="/posts/style-directives/">style directive</a> (~5k views), mostly from Google traffic—I assume it ranks well for &quot;svelte style directive.&quot; My most popular overall was <a href="/posts/svelte-$-meanings/">The many meanings of $ in Svelte</a> (~6k views), which I wrote at the end of 2021 but also gets a consistent stream of traffic from Google.</p>
<p>I also did one freelance article for an external outlet—I <a href="https://upstash.com/blog/sveltekit-edge-redis">wrote for Upstash</a> on combining SvelteKit, Upstash Redis, and Vercel edge functions.</p>
<h2>Speaking</h2>
<p>I had a few speaking appearances this year:</p>
<ul>
<li>I introduced Svelte and contributing to open source on <a href="/posts/20minjs/">20minJS</a></li>
<li>I guested on <a href="/posts/svelte-radio/">Svelte Radio</a> to talk about using Svelte at work and life so far as a core team member</li>
<li>I also introduced Svelte on the <a href="/posts/tkyt/">TKYT YouTube show</a>. This one was extra fun, since we built a small <a href="https://sw-demo-svelte.vercel.app/">Star Wars watching order app</a> and got to touch on almost all the basics of Svelte</li>
<li>I gave one remote meetup talk for <a href="/posts/svelte-london-2022/">Svelte London</a> on native page transitions in SvelteKit</li>
</ul>
<p>All were very fun, though a little stressful beforehand, since I didn't always know where the conversation would go. I usually like to do a lot of prep work, which can be time consuming. However, I'd still love to do more of this in 2023!</p>
<h2>Open source</h2>
<p>In January 2022 I was asked to join the Svelte core team, which was an incredible honor. It was very exciting to see SvelteKit develop from the inside and all the work that goes with it. Most of my contributions focused around accessibility and trying to make sure that SvelteKit and the docs site are accessible, as well as providing feedback around new Svelte/SvelteKit API features and proposals. The contribution I'm most proud of was writing up some dedicated docs around <a href="https://kit.svelte.dev/docs/accessibility">SvelteKit and accessibility</a>, and I really appreciated the <a href="https://github.com/sveltejs/kit/pull/5856">reviews on that content</a> from Melanie Sumner and Matthew Gibbons. In the new year, I definitely want to take a look at other accessible patterns we can document, as well as seeing if there's anything we can do to make our client-side routing more accessible—there's already been some Twitter feedback around that.</p>
<p>I also continued to maintain my <a href="https://github.com/geoffrich/svelte-adapter-azure-swa">SvelteKit adapter</a> for Azure Static Web Apps. Most of this was keeping up with the breaking changes in the adapter API when SvelteKit was pre-1.0. I also got some PRs from external contributors, which was fun to see! I'm hoping to get this package to 1.0 this year—there are just some configuration things to work out first.</p>
<h2>Personal life</h2>
<p>Two big things happened to me personally this year. First, my wife and I bought our first house in April, and moved in May. While there has been a ton to get done, we're very happy to have our own space. The cats took a little while to adjust, but they eventually settled right in and appreciated all the extra windows.</p>
<p>Second, in August I left Alaska Airlines, where I had been for four years, and accepted a fully-remote role at Ordergroove, a startup in the e-commerce subscription space. I hugely appreciated my time and growth at Alaska, where I was a major contributor to our open-source design system and got to introduce Svelte, but it was time to take on some new challenges. I've been enjoying the new role and look forward to everything to come this year.</p>
<p>I was also hoping to make it to Svelte Summit in Stockholm in September 2022, but unfortunately got Covid only a couple days before leaving. Hopefully I make it this year—there are so many from the Svelte community I want to meet!</p>
<p>Finally, I did rethink my approach to social media a bit with all the upheaval at Twitter. It was a good reminder that social media is ephemeral, and you should be posting anything you care about on your personal site. Going forward, I want to be more intentional about posting content here that I would previously only tweet out, like random demos. I also have <a href="https://front-end.social/@geoffrich">an account on Mastodon</a> now, and I've enjoyed being a part of the community over there. For now, I'll still be posting new articles over on Twitter (and Mastodon), but any other musings will probably stay on Mastodon. (And if you are using Mastodon, I recommend giving the <a href="https://elk.zone/">Elk</a> client a try!)</p>
<h2>Goals</h2>
<p>I have a few goals in 2023:</p>
<ul>
<li>I want to read more prose. I read 14 prose books last year; this year I'd like to at least hit 20. I used to read a lot more than this, so I just need to be more intentional with how I spend my time.</li>
<li>I want to continue to write an average of 1 blog post a month.</li>
<li>I want to give a talk somewhere (meetup/conference/etc)</li>
</ul>
<h2>Favorite media of the year</h2>
<p>On the off chance you care about my taste in media, here's what I enjoyed this year.</p>
<p>For video games, the game I played the most was the new <strong>Lego Star Wars</strong>, which was a blast (though I wouldn't have sunk nearly as many hours in it if I wasn't also listening to a podcast and recovering from Covid at the same time.) Even though it didn't come out this year, I also played a lot of <strong>Picross 3D</strong> for the Nintendo DS, another good podcast game. <em>Honorable mentions: Return to Monkey Island, Shovel Knight Dig, 999 (from 2010)</em></p>
<p>Speaking of podcasts, my podcast listening time has gone down a bit since I stopped commuting, which is a blessing and a curse. Thankfully (?), the new house doesn't have a dishwashwer, so the dishwashing time has been making up for it. My absolute favorite podcasts have been <strong>Svelte Radio</strong> (of course), <strong>My Marvelous Year</strong> (a book club going through a curated list of Marvel comics), and <strong>A More Civilized Age</strong> (a Star Wars: The Clone Wars podcast)—these are the podcasts I listen to every episode of. <em>Honorable mentions: Strong Songs (music analysis), Triple Click &amp; Waypoint Radio (gaming), Shop Talk Show (web dev), Off Panel (comics interviews), Mangasplaining (manga book club)</em></p>
<p>For movies, the most fun I had in a theater was <strong>Glass Onion</strong> which I saw with the whole family after Thanksgiving. Just a wonderful rollercoaster of a whodunnit that never quite goes where you expect. <strong>RRR</strong> was also an Experience, though in a completely different way. In the <a href="https://letterboxd.com/patrickhwillems/film/rrr/1/">words of Patrick Willems</a>, &quot;the best movie ever made about fighting colonialism with dance battles and armies of rampaging animals and most of all, friendship.&quot; And I also watched the <strong>Mission Impossible</strong> series for the first time, which was a great way to recover from Covid. <em>Honorable mentions: Everything Everywhere All At Once, Avatar: The Way of Water, Nope, The Unbearable Weight of Massive Talent, Turning Red, AmbuLAnce</em></p>
<p>For TV, my show of the year had to be <strong>Star Wars: Andor</strong>, an extremely well-written and well-paced show about the rise of the rebellion. Having it released weekly enriched the experience, since the anticipation and discussion between episodes was part of the fun. That prison arc on Narkina 5... hoo boy. <em>Honorable mentions: Severance, Our Flag Means Death, Only Murders in the Building, The Rehearsal, The Boys, House of the Dragon</em></p>
<p>I didn't read as much prose as I'd like this year, but my favorite book was <strong>Locklands</strong> by Robert Jackson Bennett, which concluded The Founders trilogy. I really recommend the trilogy if you like Sci-Fi/Fantasy books, especially if you are in the software field—the magic system in these books revolves around &quot;scriving&quot;, essentially programming objects to do extraordinary things. From Tor.com's <a href="https://www.tor.com/2018/08/20/book-reviews-foundryside-by-robert-jackson-bennett/">review of the first book</a>:</p>
<blockquote>
<p>Utilizing a complicated alphabet left behind by ancient, almost mythological figures called the Hierophants, mankind has figured out how to imbue everyday objects with something akin to sentience and convince these objects to do work for them. Some scrivings can convince wheels to move across flat surfaces as though they were rolling downhill. Others tell a sword that it is as sharp as ten blades in one, capable of cutting through nearly anything. Others tell a candle that it can never burn out. And in this world of scrivings, the four major Merchant Houses all vie for dominance in their enormous campuses, while outside of them, people like Sancia Grado cobble together enough to survive.</p>
</blockquote>
<p>I probably would have enjoyed it more if I had been able to remember what happened in the previous two, though (which I read two years ago). <em>Honorable mentions: Star Wars: Brotherhood, The Lost Metal, Leviathan Falls</em></p>
<p>I did read a lot of comics, though—mostly along with various &quot;book club&quot;-style podcasts I listen to (so a selection of manga and Marvel comics 1994-2000), but also some self-directed reading. My favorite was James Tynion IV's <strong>The Nice House on the Lake</strong>, where old friends are invited to a lake house for a getaway, and well... things happen. To say any more would ruin the experience, so if you have any interest at all, I recommend reading it without looking up more about it. I'm really looking forward to the second (concluding) volume later this year. I also read a bunch of the manga <strong>Blue Giant</strong> about a rising jazz saxophonist, which was another great read. <em>Honorable mentions: A.X.E.: Judgment Day, MacKay's Moon Knight, Cantwell's Iron Man, Newburn</em></p>
<p>Most of the music I listen to is video game music. My favorite soundtrack of the year was <strong>Kirby and the Forgotten Land</strong>, which varies between the <a href="https://youtu.be/cmxR6-F84l8">kind of cheerful music</a> you'd expect from a Kirby game to a 5+ minute <a href="https://youtu.be/ZIl1rmHwgoM">prog jazz epic</a>. I also really enjoyed <strong>Shovel Knight Dig</strong>, which had a bunch of energetic Sega-inspired chiptunes—Jake Kaufman always delivers the goods. And of course, <strong>Xenoblade Chronicles 3</strong> delivered a heaping portion (10, 11 hours?) of some of the best JRPG music ever. <em>Honorable mentions: Pokemon Scarlet/Violet, Sonic Frontiers, TUNIC</em></p>
<p>On the non-VGM front, my favorite album was Jukebox the Ghost's <strong>Cheers</strong>, a wonderful album of Queen-inspired piano-driven rock. I also played a lot of Carlos Eiene's debut <strong>Insane in the Rain</strong>, a collection of original jazz fusion tunes that nonetheless are influenced by the style of Japanese video game music.</p>
<h2>Wrapping up</h2>
<p>So that's the year that was—hard to believe that was all this year! Here's to an even better 2023.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Building tic-tac-toe with Svelte</title>
    <link href="https://geoffrich.net/posts/tic-tac-toe/"/>
    <updated>2022-11-20T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/tic-tac-toe/</id>
    
    <content type="html"><![CDATA[
      <p>I recently came across <a href="https://adventofvue.com/">Advent of Vue</a>, which looks like they'll have some fun front-end challenges for December. While they're Vue-focused, I assume any front-end tech should work. I'll be attempting them with Svelte &amp; SvelteKit.</p>
<p>They sent out an early challenge to recreate the game tic-tac-toe. <a href="https://advent-of-sveltekit-2022.vercel.app/day/0">Here's the solution</a> I came up with. The <a href="https://github.com/geoffrich/advent-of-sveltekit-2022/tree/main/src/routes/day/0">code is on GitHub</a>, and I'll briefly explain it below. While building tic-tac-toe may seem simple, there are some interesting wrinkles, especially with regards to accessibility.</p>
<h2>Rendering the board</h2>
<p>I represented the board as a 3x3 array.</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">let</span> board <span class="token operator">=</span> <span class="token punctuation">[</span><br>  <span class="token punctuation">[</span>Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">]</span><span class="token punctuation">,</span><br>  <span class="token punctuation">[</span>Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">]</span><span class="token punctuation">,</span><br>  <span class="token punctuation">[</span>Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">]</span><br><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Each item in the array is a TypeScript enum - either <code>Empty</code> if the space is empty, or <code>X</code> or <code>O</code> if the space is occupied.</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">enum</span> Move <span class="token punctuation">{</span><br>  <span class="token constant">X</span> <span class="token operator">=</span> <span class="token string">'X'</span><span class="token punctuation">,</span><br>  <span class="token constant">O</span> <span class="token operator">=</span> <span class="token string">'O'</span><span class="token punctuation">,</span><br>  Empty <span class="token operator">=</span> <span class="token string">''</span><br><span class="token punctuation">}</span></code></pre>
<p>In the template, we iterate over this array using <code>#each</code> loops.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">board </span><span class="token keyword">as</span> <span class="token language-javascript">row<span class="token punctuation">,</span> r<span class="token punctuation">}</span></span></span><br>    <span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">row </span><span class="token keyword">as</span> <span class="token language-javascript">col<span class="token punctuation">,</span> c<span class="token punctuation">}</span></span></span><br>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cell<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>            <span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> col <span class="token operator">!==</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">}</span></span><br>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Icon</span> <span class="token attr-name">move=</span><span class="token language-javascript"><span class="token punctuation">{</span>col<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>            <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EmptyCell</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">place</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> c<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token attr-name">disabled=</span><span class="token language-javascript"><span class="token punctuation">{</span>state <span class="token operator">!==</span> State<span class="token punctuation">.</span>Playing<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>visually-hidden<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Place row <span class="token language-javascript"><span class="token punctuation">{</span>r <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span></span> column <span class="token language-javascript"><span class="token punctuation">{</span>c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>EmptyCell</span><span class="token punctuation">></span></span><br>            <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>    <span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>The <code>&lt;EmptyCell&gt;</code> is its own component for easier colocation of styles, but it's essentially a <code>&lt;button&gt;</code>. It represents an empty space on the board that the player can place their token on.</p>
<p>It's important that we use a <code>&lt;button&gt;</code> here. This helps ensure that people can interact with it even if they're using a keyboard or other assistive technology - a <code>&lt;div on:click&gt;</code> would <a href="https://benmyers.dev/blog/clickable-divs/">not be the same</a>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> disabled <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click</span> <span class="token language-javascript"><span class="token punctuation">{</span>disabled<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">button</span> <span class="token punctuation">{</span><br>		<span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br>		<span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br>		<span class="token property">appearance</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br>		<span class="token property">border</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br>		<span class="token property">background</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br>		<span class="token property">border-radius</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--border-size-3<span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token selector">button:hover,<br>	button:focus-visible</span> <span class="token punctuation">{</span><br>		<span class="token property">background-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--gray-2<span class="token punctuation">)</span><span class="token punctuation">;</span><br>		<span class="token property">box-shadow</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--shadow-3<span class="token punctuation">)</span><span class="token punctuation">;</span><br>		<span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token selector">button:focus-visible</span> <span class="token punctuation">{</span><br>		<span class="token property">outline</span><span class="token punctuation">:</span> solid <span class="token function">var</span><span class="token punctuation">(</span>--svelte<span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token selector">button:disabled</span> <span class="token punctuation">{</span><br>		<span class="token property">cursor</span><span class="token punctuation">:</span> not-allowed<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>Note the <code>on:click</code> - this instructs Svelte to <a href="https://svelte.dev/tutorial/event-forwarding">forward the event</a> so we can listen to the click event on the parent <code>&lt;EmptyCell&gt;</code> component.</p>
<p>Also, since we use a <code>&lt;slot&gt;</code>, the content inside <code>&lt;EmptyCell&gt;</code> will be placed inside the component's <code>&lt;button&gt;</code> element.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EmptyCell</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">place</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> c<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token attr-name">disabled=</span><span class="token language-javascript"><span class="token punctuation">{</span>state <span class="token operator">!==</span> State<span class="token punctuation">.</span>Playing<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>visually-hidden<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Place row <span class="token language-javascript"><span class="token punctuation">{</span>r <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span></span> column <span class="token language-javascript"><span class="token punctuation">{</span>c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>EmptyCell</span><span class="token punctuation">></span></span></code></pre>
<p>The <code>&lt;span class=&quot;visually-hidden&quot;&gt;</code> gives our <code>&lt;button&gt;</code> a label. Even though we don't want any text to show visually, we still need to give the button a unique name so that assistive technology understands its purpose. If you haven't seen this technique before, this is a good article on <a href="https://www.sarasoueidan.com/blog/accessible-icon-buttons/#technique-%231%3A-accessible-visually-hidden-text">visually-hidden and buttons</a>.</p>
<p>In our case, we are uniquely identifying each button with the row and column it's at. For example, the middle space will have the name &quot;Place row 2 column 2&quot;.</p>
<h2>Playing the game</h2>
<p>The click handler on the <code>&lt;EmptyCell&gt;</code> above calls <code>place</code> with the cell's row and column index. <code>place</code> updates the board at that position and swaps whose turn it is. It also manages focus, but we'll take a closer look at that in a bit.</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">place</span><span class="token punctuation">(</span>row<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> col<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  board<span class="token punctuation">[</span>row<span class="token punctuation">]</span><span class="token punctuation">[</span>col<span class="token punctuation">]</span> <span class="token operator">=</span> turn<span class="token punctuation">;</span><br>  turn <span class="token operator">=</span> turn <span class="token operator">===</span> Move<span class="token punctuation">.</span><span class="token constant">O</span> <span class="token operator">?</span> Move<span class="token punctuation">.</span><span class="token constant">X</span> <span class="token operator">:</span> Move<span class="token punctuation">.</span><span class="token constant">O</span><span class="token punctuation">;</span><br>  <span class="token function">tick</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>focusNextAvailableTile<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Because we update <code>board</code>, the reactive statements to update the <code>winner</code> and <code>state</code> also run.</p>
<pre class="language-js"><code class="language-js"><span class="token literal-property property">$</span><span class="token operator">:</span> winner <span class="token operator">=</span> <span class="token function">checkWinner</span><span class="token punctuation">(</span>board<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> state <span class="token operator">=</span> <span class="token function">getGameState</span><span class="token punctuation">(</span>winner<span class="token punctuation">,</span> board<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>It's very nice to be able to declaratively write reactive logic like this. Instead of having to remember to check if there's a winner in every function we update the board, we <em>instead</em> say &quot;hey, whenever <code>board</code> changes, update <code>winner</code> again.&quot; This means when we clear the board in <code>reset</code>, <code>winner</code> and <code>state</code> will automatically update too.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">reset</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  board <span class="token operator">=</span> <span class="token function">getEmptyBoard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token comment">// no need to set winner and state here</span><br>  <span class="token function">tick</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>focusNextAvailableTile<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Here's how we determine the winner of the tic tac toe game. We check all possible winning states and return the winner if there is one. (There's probably a more elegant way to do it, but this gets the job done.)</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">checkWinner</span><span class="token punctuation">(</span>board<span class="token operator">:</span> Move<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> row <span class="token keyword">of</span> board<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>row<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>v <span class="token operator">=></span> v <span class="token operator">===</span> Move<span class="token punctuation">.</span><span class="token constant">X</span><span class="token punctuation">)</span> <span class="token operator">||</span> row<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>v <span class="token operator">=></span> v <span class="token operator">===</span> Move<span class="token punctuation">.</span><span class="token constant">O</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">return</span> row<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">===</span> board<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">return</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">===</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">===</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> board<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">===</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> board<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">==</span> board<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>We also update the &quot;game state&quot;. The tic-tac-toe game is either in the <code>Won</code>, <code>Draw</code>, or <code>Playing</code> state. This helps keep logic about the state of the game consistent - instead of checking <code>winner</code> and <code>board</code> in multiple places, we only need to check the <code>state</code> variable.</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getGameState</span><span class="token punctuation">(</span>winner<span class="token operator">:</span> Move <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> board<span class="token operator">:</span> Move<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>winner<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> State<span class="token punctuation">.</span>Won<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>board<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>row <span class="token operator">=></span> row<span class="token punctuation">.</span><span class="token function">every</span><span class="token punctuation">(</span>col <span class="token operator">=></span> col <span class="token operator">!==</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> State<span class="token punctuation">.</span>Draw<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> State<span class="token punctuation">.</span>Playing<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>The state is mainly used to display a message below the game board.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>this=</span><span class="token language-javascript"><span class="token punctuation">{</span>statusEl<span class="token punctuation">}</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>-1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>    <span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> state <span class="token operator">===</span> State<span class="token punctuation">.</span>Won<span class="token punctuation">}</span></span><br>        <span class="token language-javascript"><span class="token punctuation">{</span>winner<span class="token punctuation">}</span></span> won.<br>    <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span> <span class="token keyword">if</span> state <span class="token operator">===</span> State<span class="token punctuation">.</span>Draw<span class="token punctuation">}</span></span><br>        It's a draw!<br>    <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br>        It's <span class="token language-javascript"><span class="token punctuation">{</span>turn<span class="token punctuation">}</span></span>'s turn<br>    <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> state <span class="token operator">!==</span> State<span class="token punctuation">.</span>Playing<span class="token punctuation">}</span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span>reset<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>Play again?<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<h2>Focus management</h2>
<p>When a move is made, we replace the <code>&lt;button&gt;</code> with an icon showing which player moved there.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> col <span class="token operator">!==</span> Move<span class="token punctuation">.</span>Empty<span class="token punctuation">}</span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Icon</span> <span class="token attr-name">move=</span><span class="token language-javascript"><span class="token punctuation">{</span>col<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>EmptyCell</span> <span class="token punctuation">/></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>This causes problems for keyboard and other assistive tech users because their focus will be lost abruptly. Instead, we should intentionally move focus somewhere after each player's move. For this project, I chose to move focus to the next available spot, or to the status element if there are no spaces left (i.e. the game has ended).</p>
<pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">place</span><span class="token punctuation">(</span>row<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> col<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  board<span class="token punctuation">[</span>row<span class="token punctuation">]</span><span class="token punctuation">[</span>col<span class="token punctuation">]</span> <span class="token operator">=</span> turn<span class="token punctuation">;</span><br>  turn <span class="token operator">=</span> turn <span class="token operator">===</span> Move<span class="token punctuation">.</span><span class="token constant">O</span> <span class="token operator">?</span> Move<span class="token punctuation">.</span><span class="token constant">X</span> <span class="token operator">:</span> Move<span class="token punctuation">.</span><span class="token constant">O</span><span class="token punctuation">;</span><br>  <span class="token function">tick</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>focusNextAvailableTile<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token keyword">function</span> <span class="token function">focusNextAvailableTile</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> nextTile <span class="token operator">=</span> boardEl<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'button:not(:disabled)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>nextTile<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token punctuation">(</span>nextTile <span class="token keyword">as</span> HTMLElement<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    statusEl<span class="token punctuation">.</span><span class="token function">focus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>A few things to note:</p>
<ol>
<li>We use Svelte's <code>tick</code> function. This returns a Promise that resolves after all state updates have been applied. If we <em>didn't</em> use <code>tick</code>, then we would move focus too early. <code>board</code> would be updated, but the DOM would not reflect those updates yet, so we might move focus to a space that will be removed on the next tick.</li>
<li>We're using <code>querySelector</code> to get the first available <code>button</code> that isn't disabled. Using <code>querySelector</code> in Svelte is <a href="/posts/clean-component-tips/#heading-work-with-the-component-template-not-against-it">usually an antipattern</a>, but in this case it makes a lot of sense. First, we are scoping it to the <code>boardEl</code> so that it only applies to elements inside the board. This is much safer than <code>document.querySelector</code>, which could return nodes anywhere in the document. Second, this is a much more efficient way to get the node. We could try to determine with JS which space is the first available one, and then call <code>focus</code> on a bound <code>EmptyCell</code> component corresponding to that space, which would call a <code>focus</code> method we expose inside that component. But that's a lot to wire up. That's not to say using <code>querySelector</code> is not without risk - if this was a production application, someone else refactoring the <code>EmptyCell</code> could unexpectedly break this logic. But for this demo, I think it's the best choice.</li>
<li>If no spaces are available, we focus the status element instead. This is a sensible place to move focus since if no spaces are available, it will announce who won or lost. The status element is a div, which can't normally receive focus, so we set <code>tabindex=&quot;-1&quot;</code> on it. This makes it so the element can be programatically focused (like we're doing now), but it won't be focused by someone tabbing through the document normally.</li>
</ol>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>this=</span><span class="token language-javascript"><span class="token punctuation">{</span>statusEl<span class="token punctuation">}</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>-1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<h2>Wrapping up</h2>
<p>I hope that was a useful look at how I built something like this. I'm planning to work through the rest of the Advent challenges as they're released throughout December (though no guarantees). I'll be sharing them out as I go on <a href="https://front-end.social/@geoffrich">Mastodon</a> and/or <a href="https://twitter.com/geoffrich_">Twitter</a>.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Create dynamic social card images with Svelte components</title>
    <link href="https://geoffrich.net/posts/svelte-social-image/"/>
    <updated>2022-10-18T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-social-image/</id>
    
    <content type="html"><![CDATA[
      <div class="callout">
<p>This post was updated in January 2024 to use SvelteKit's new <code>read</code> method to read font data on the server.</p>
</div>
<p>When you share a blog post or site on social media, the first thing most people will see is a social card image, or &quot;og:image&quot;. Adding one of these can massively increase the number of people who engage with your post. In the past, you either had to manually create these images or automatically generate them with a headless browser or service. However, Vercel recently <a href="https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images">launched a library</a> that makes generating these images easier, faster, and cheaper. In this post, I’ll show you how you can use that library to generate social card images using Svelte components. Even though I’m focusing on Svelte, the concepts should transfer to any component framework that you can render to an HTML string.</p>
<h2>What is og:image?</h2>
<p>A deep dive on this is outside the scope of this blog post, but I’ll give the tl;dr. An og:image is a type of <code>&lt;meta&gt;</code> tag that you put in your site’s <code>&lt;head&gt;</code>. Social media sites will use this information to display the given image when you share that URL. For instance, one of my recent blog posts looked like this on Twitter:</p>
<img alt="Screenshot of tweet. Text is: New blog post! I wrote about how I used the shared element transition API to implement page transitions in SvelteKit. This is part 1 - part 2 will be out in the next few weeks and if you saw my Svelte London talk, you already got a sneak peek! Social image shows a image of a strawberry transitioning from one page to the next." src="/images/svelte-social-image/tweet.png" style="width: 100%; max-width: 500px">
<p><a href="https://www.swyx.io/jamstack-og-images">Swyx has a blog post</a> that does a good job explaining what they are and why you would want them on your site (though the information around generating them is out of date).</p>
<p>While there are other meta tags to display information like title, author, description, and so on, we’ll focus on the image today.</p>
<p>Previously, if you wanted an og:image on your site, you had a few options:</p>
<ul>
<li>you could choose a single static image to show for every page on your site. This was easy, but boring.</li>
<li>you could create an image for each page manually.</li>
<li>you could use a <a href="https://www.learnwithjason.dev/blog/auto-generate-social-image">hosted service like Cloudinary</a> to do it for you.</li>
<li>you could automate og:image generation using <a href="https://pptr.dev/">Puppeteer</a> to take screenshots of Headless Chrome. This let you write your templates as HTML and CSS, making it easy to update your template, but it could be slow. Chrome is also too large to fit in a serverless function, so you couldn’t host on common providers like Vercel or Netlify. GitHub uses this <a href="https://github.blog/2021-06-22-framework-building-open-graph-images/#some-performance-gotchas">method</a> to generate social share images for repos, issues, and PRs, but they also had to work around performance issues</li>
</ul>
<p>In the past I tried using Puppeteer to generate my images, but ran into issues running locally (granted, I was on an old version of WSL at the time). I currently use the second option—I have a Figma template and update it for each post. This works, but it’s tedious. It is also time-consuming if I ever wanted to update my template for all previous posts. Some of my older blog posts still have an old card design that I don’t like but I haven’t updated them due to the time commitment.</p>
<p>Wouldn’t it be nice if there was an automated solution that was fast, easily deployable, and that could be configured using modern component syntax? Well…</p>
<h2>Introducing <code>@vercel/og</code></h2>
<p>Last week Vercel <a href="https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images">launched a new library</a> to generate social card images. Instead of spinning up headless Chrome and taking screenshots, this library library converts HTML and a subset of CSS into images. This makes for a solution that is easier (you’re just writing HTML), 5x faster, and 100x more lightweight than running headless Chrome. For more tech details, see <a href="https://twitter.com/shuding_/status/1579607964549513217">this Twitter thread</a> by creator Shu Ding.</p>
<p>When I saw this my first thought was: how can I use this in SvelteKit, and with a Svelte component? Since I use Svelte for my personal projects, it would be a very nice dev experience to write my social images in Svelte.</p>
<p>Unfortunately, all the <a href="https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples">official examples</a> use JSX syntax and I didn’t want to take a dependency on React. But it is possible—you just have to wire some things together. Here is what we’ll be working with:</p>
<ol>
<li>The key dependency here is <a href="https://github.com/vercel/satori">Satori</a>, the underlying library powering <code>@vercel/og</code>. It does the hardest part: taking HTML (well, JSX) and turning it into an SVG. It only accepts JSX, so you can either manually create <a href="https://github.com/vercel/satori#use-without-jsx">React-like objects</a> (e.g. <code>{ type: div, props: { children: &quot;Hello world&quot; } }</code> ), or you can…</li>
<li>Use <a href="https://github.com/natemoo-re/satori-html">satori-html</a>, a library from <a href="https://astro.build/">Astro’s</a> Nate Moore that will turn an HTML string into an object Satori can use. You can write the HTML string manually, but we’ll be generating it from a Svelte component instead.</li>
<li>To get the HTML string from the Svelte component, you can import the component on the server and call <code>.render()</code> to get the HTML representation. This is possible via Svelte’s <a href="https://svelte.dev/docs#run-time-server-side-component-api">server-side component API</a>. This step is also very performant, since rendering a Svelte component server-side is just string concatenation.</li>
<li>Finally, once we create an SVG from the HTML string using Satori, we can turn that into a PNG using <a href="https://github.com/yisibl/resvg-js">resvg-js</a>.</li>
</ol>
<p>So the code for this ends up looking something like this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> componentResult <span class="token operator">=</span> component<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> htmlString <span class="token operator">=</span> <span class="token function">satoriHtml</span><span class="token punctuation">(</span>componentResult<span class="token punctuation">.</span>html<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> svg <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">satori</span><span class="token punctuation">(</span>htmlString<span class="token punctuation">,</span> satoriConfig<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> image <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Resvg</span><span class="token punctuation">(</span>svg<span class="token punctuation">,</span> resvgConfig<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span>image<span class="token punctuation">.</span><span class="token function">asPng</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br>	<span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>		<span class="token string-property property">'content-type'</span><span class="token operator">:</span> <span class="token string">'image/png'</span><br>	<span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>While the details here can get a little tricky, that’s not a lot of code! The pipeline ends being: Svelte → HTML → JSX → SVG → PNG. Whew!</p>
<p>(There might be a way to use <code>@vercel/og</code> directly with the same satori-html transform. This would give us less to implement, since <code>@vercel/og</code> will automatically construct a PNG response and allow you to use emoji, other languages, and Tailwind CSS. However, I ran into WASM errors when running locally with Vite. Vercel also specifies that <code>@vercel/og</code> is only compatible with the <a href="https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation#limits">Edge Runtime</a>.)</p>
<p>With the overview out of the way, let’s drill into the details.</p>
<blockquote>
<p>Warning: both Satori and satori-html are pre-v1, so there could be breaking changes. This demo has been developed with <code>satori@0.10.11</code> and <code>satori-html@0.3.2</code>.</p>
</blockquote>
<h2>Getting started</h2>
<p>To follow along, create a new SvelteKit app following the instructions on the SvelteKit homepage. Select the skeleton project template when prompted. I’ll be using JavaScript (with JSDoc) for this post, but you can select TypeScript if you like.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> create svelte@latest sveltekit-og<br><span class="token builtin class-name">cd</span> sveltekit-og<br><span class="token function">npm</span> <span class="token function">install</span><br><span class="token function">npm</span> run dev -- --open</code></pre>
<p>We’ll need a few dependencies, as mentioned above:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> i satori satori-html @resvg/resvg-js -D</code></pre>
<p>Satori also requires you to BYOF (bring your own fonts). It will use the raw font data to render any text to SVG paths. For this demo, download the <a href="https://fonts.google.com/share?selection.family=Noto%20Sans">Noto Sans font</a> from Google Fonts. Unzip the folder and place the <code>NotoSans-Regular.ttf</code> file inside a folder at <code>/src/lib</code>.</p>
<p>Now that we have all the dependencies, let’s create a server endpoint that will generate OG images. We’ll start small, and just pass a &quot;hello world&quot; string for now. Once we get the basic image generation setup working, we can add a Svelte component.</p>
<p>Create a new file at <code>src/routes/og/+server.js</code> and paste in the following code:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> satori <span class="token keyword">from</span> <span class="token string">'satori'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>Resvg<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@resvg/resvg-js'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> NotoSans <span class="token keyword">from</span> <span class="token string">'$lib/NotoSans-Regular.ttf'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>read<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/server'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> fontData <span class="token operator">=</span> <span class="token function">read</span><span class="token punctuation">(</span>NotoSans<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">arrayBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> height <span class="token operator">=</span> <span class="token number">630</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> width <span class="token operator">=</span> <span class="token number">1200</span><span class="token punctuation">;</span><br><br><span class="token comment">/** @type {import('./$types').RequestHandler} */</span><br><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">GET</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> html <span class="token operator">=</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span><br>    <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token string">'hello, world'</span><span class="token punctuation">,</span><br>      <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token punctuation">{</span><span class="token literal-property property">color</span><span class="token operator">:</span> <span class="token string">'red'</span><span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> svg <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">satori</span><span class="token punctuation">(</span>html<span class="token punctuation">,</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">fonts</span><span class="token operator">:</span> <span class="token punctuation">[</span><br>      <span class="token punctuation">{</span><br>        <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Noto Sans'</span><span class="token punctuation">,</span><br>        <span class="token literal-property property">data</span><span class="token operator">:</span> <span class="token keyword">await</span> fontData<span class="token punctuation">,</span><br>        <span class="token literal-property property">style</span><span class="token operator">:</span> <span class="token string">'normal'</span><br>      <span class="token punctuation">}</span><br>    <span class="token punctuation">]</span><span class="token punctuation">,</span><br>    height<span class="token punctuation">,</span><br>    width<br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> resvg <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Resvg</span><span class="token punctuation">(</span>svg<span class="token punctuation">,</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">fitTo</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">mode</span><span class="token operator">:</span> <span class="token string">'width'</span><span class="token punctuation">,</span><br>      <span class="token literal-property property">value</span><span class="token operator">:</span> width<br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> image <span class="token operator">=</span> resvg<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span>image<span class="token punctuation">.</span><span class="token function">asPng</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br>    <span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>      <span class="token string-property property">'content-type'</span><span class="token operator">:</span> <span class="token string">'image/png'</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>This creates a <a href="https://kit.svelte.dev/docs/routing#server">SvelteKit server route</a> that will generate OG images for us. We're using SvelteKit's <a href="https://kit.svelte.dev/docs/modules#$app-server-read">read</a> function to retrieve the raw font data. For more on how this function works, see <a href="/posts/sveltekit-read/">my post about it</a>.</p>
<div class="callout">
<p>This post originally used a custom Vite plugin to retrieve the font data since it was written before the <code>read</code> function existed. If you're using a SvelteKit version that doesn't support <code>read</code> (&lt;2.4), you can reverse the changes in <a href="https://github.com/geoffrich/sveltekit-og-post/pull/1/commits/3cf96e3a281463e03545bf3df7b96e14aeb72dda">this PR</a> to get back to the old method.</p>
</div>
<p>With that change, you should be able to run the app and navigate to <code>/og</code> and see the generated &quot;Hello, world!&quot; image.</p>
<p><img src="/images/svelte-social-image/step-1.png" alt="A social image with hello world in small text in the top right"></p>
<h2>Wiring this up to a Svelte component</h2>
<p>Now that we have the basics working, let’s generate our markup from a Svelte component instead. Create a <code>Card.svelte</code> file inside the <code>src/lib</code> folder and move our &quot;Hello, world!&quot; content in there.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">color</span><span class="token punctuation">:</span> red</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>Hello, world!<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>We can import this component on the server and use it to generate our HTML instead of writing it by hand. We’ll use the excellent satori-html library I had you install previously. Add the following imports to the top of our <code>+server.js</code>…</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>html <span class="token keyword">as</span> toReactNode<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'satori-html'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> Card <span class="token keyword">from</span> <span class="token string">'$lib/Card.svelte'</span><span class="token punctuation">;</span></code></pre>
<p>…and replace the hard-coded <code>element</code> definition with the following lines:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> result <span class="token operator">=</span> Card<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> element <span class="token operator">=</span> <span class="token function">toReactNode</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>result<span class="token punctuation">.</span>html<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;style></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>result<span class="token punctuation">.</span>css<span class="token punctuation">.</span>code<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/style></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This renders the Card component to an object containing the raw HTML and CSS strings and uses satori-html to convert it to a JSX object.</p>
<p>If you navigate to <code>/og</code> you should see the same card as before—but it’s now being generated from a Svelte component! Let’s improve the card design by adding some styles. Satori normally only accepts inline styles, but satori-html will automatically convert CSS in a style tag to the format Satori expects.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">div</span> <span class="token punctuation">{</span><br>		<span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">'Noto Sans'</span><span class="token punctuation">;</span><br>		<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span><br>		<span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span><br>		<span class="token property">font-size</span><span class="token punctuation">:</span> 50px<span class="token punctuation">;</span><br>		<span class="token property">background-color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><br>		<span class="token property">height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br>		<span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span><br>		<span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span><br>		<span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>(Be advised that Satori only implements <a href="https://github.com/vercel/satori#css">a subset of CSS</a>. In particular, you can’t use CSS Grid, only Flexbox. However, it’s usually enough to get the job done.)</p>
<p>After adding those styles, refresh the page to see the new social card. Endpoints don’t hot reload, so you’ll have to refresh the page yourself.</p>
<p><img src="/images/svelte-social-image/step-2.png" alt="A social image with hello world centered in large text"></p>
<p>One nice thing about writing these styles as a Svelte component is that you can iterate quicker by rendering the component on a page, since pages will be hot-reloaded without needing to manually refresh. This is also easier to debug. Add the following to <code>src/routes/+page.svelte</code></p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> Card <span class="token keyword">from</span> <span class="token string">'$lib/Card.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Card</span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token atrule"><span class="token rule">@font-face</span></span> <span class="token punctuation">{</span><br>		<span class="token property">font-family</span><span class="token punctuation">:</span> <span class="token string">'Noto Sans'</span><span class="token punctuation">;</span><br>		<span class="token property">src</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span><span class="token string url">'/src/lib/NotoSans-Regular.ttf'</span><span class="token punctuation">)</span></span><span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token selector">.card</span> <span class="token punctuation">{</span><br>		<span class="token property">height</span><span class="token punctuation">:</span> 630px<span class="token punctuation">;</span><br>		<span class="token property">width</span><span class="token punctuation">:</span> 1200px<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>I’m hard-coding the card dimensions and linking up the font to match the Satori environment as closely as possible.</p>
<p>Now if you change the styles on the Card component (for example, changing <code>background-color</code> to lightblue), this page will update without refreshing. You can also inspect the component using the browser devtools to debug any CSS issues. While this is useful while developing, make sure to check the actual card at <code>/og</code>, since there will be differences due to the CSS limitations mentioned previously. Satori also has a <a href="https://github.com/vercel/satori#debug">debug option</a> if you need to debug the rendered card.</p>
<h2>Using component props</h2>
<p>So now we have an endpoint generating a social image, but it’s static—it will return the same image every time. Let’s change that. We can dynamically change the text displayed based on a query parameter. For instance, let's make it so requesting <code>/og?message=Welcome</code> will display the text &quot;Welcome&quot; instead.</p>
<p>First, let’s expose a prop from our Svelte component to set this text. We’ll default it to the text we had before.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> message <span class="token operator">=</span> <span class="token string">'Hello, world!'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">color</span><span class="token punctuation">:</span> red</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>{message}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>Then, we need to get the message from the query and pass it to the <code>render</code> function.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">/** @type {import('./$types').RequestHandler} */</span><br><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">GET</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>url<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> message <span class="token operator">=</span> url<span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> result <span class="token operator">=</span> Card<span class="token punctuation">.</span><span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">{</span>message<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token comment">// the rest as before</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>(We have to default <code>message</code> to <code>undefined</code> since it will be <code>null</code> if it wasn’t passed in the URL. If we pass <code>{message: null}</code> to the component, it will display <code>null</code> instead of our default value.)</p>
<p>Then if we go to <code>/og</code> without passing query params, we’ll get the same image as before…</p>
<p><img src="/images/svelte-social-image/step-3.png" alt="A social image with hello world centered in large text"></p>
<p>…but going to <code>/og?message=Welcome!</code> will display &quot;Welcome!&quot; instead.</p>
<p><img src="/images/svelte-social-image/step-4.png" alt="A social image with &quot;Welcome&quot; centered in large text"></p>
<h2>Putting it all together</h2>
<p>This is where the real power of a dynamic og:image endpoint comes into play. Because we have a parameterized endpoint, we can reuse it to generate images for multiple different pages. So in our root <code>+page.svelte</code> we might show an image that says &quot;Home&quot;…</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- /src/routes/+page.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Home<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://sveltekit-og-post.vercel.app/og?message=Home<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>og:image<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span></code></pre>
<p>…but show an image with a different message for our &quot;About&quot; page.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- /src/routes/about/+page.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>About<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://sveltekit-og-post.vercel.app/og?message=About this site<span class="token punctuation">"</span></span> <span class="token attr-name">property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>og:image<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span></code></pre>
<p>All using the same endpoint!</p>
<p>(Note that the URL is the full URL of the deployed site—the image won’t show on social previews if you use a relative URL.)</p>
<p>This can be incredibly useful for something like blog posts, where you could create a social image template for each post and just change the title for each one. And this can all be done through a single endpoint that’s located with the rest of your code and is easily customized with Svelte.</p>
<p>You can find the final code <a href="https://github.com/geoffrich/sveltekit-og-post">on my GitHub</a>.</p>
<h2>Wrapping up</h2>
<p>The card example in this post was intentionally super simple to focus on the generation side of things. Make sure to check out the <a href="https://github.com/vercel/satori">Satori docs</a> to see how you can also add images, emoji, and other languages. And of course, because you have HTML, CSS, and SVG available, just about anything you could do on the web you can now do in a social card (within Satori’s CSS limitations, of course).</p>
<p>Here are some other fun examples that use Satori:</p>
<ul>
<li>The <a href="https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images#dynamic-ticket-images-for-next.js-conf">tickets for Next.js Conf</a> generated a social card for each attendee</li>
<li>I <a href="https://sveltekit-satori.vercel.app/">made a demo</a> that generates a random pattern of colored dots based on a seed in the URL</li>
<li>My <a href="https://marvel.geoffrich.net/">Marvel Comics side project</a> creates <a href="https://github.com/geoffrich/marvel-by-year/pull/8">a social card for each year of comics</a> with a random selection of covers from that year</li>
<li>It’s not just social card images—Satori can also be used to make a <a href="https://satori-syntax-highlighter.vercel.app/">syntax highlighter</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Partial hydration in SvelteKit with @11ty/is-land</title>
    <link href="https://geoffrich.net/posts/sveltekit-is-land/"/>
    <updated>2022-10-09T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/sveltekit-is-land/</id>
    
    <content type="html"><![CDATA[
      <p>My webdev project last weekend: I got partial hydration working in a SvelteKit site using the <code>&lt;is-land&gt;</code> custom element from <a href="https://www.11ty.dev/docs/plugins/partial-hydration/">Eleventy</a>! This means it's possible to only download JS for specific components instead of the whole page. Here's what the integration currently looks like:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> Count <span class="token keyword">from</span> <span class="token string">'$lib/islands/Count.svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> Island <span class="token keyword">from</span> <span class="token string">'$lib/Island.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Without props<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Island</span> <span class="token attr-name">component=</span><span class="token language-javascript"><span class="token punctuation">{</span>Count<span class="token punctuation">}</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Count<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>With props<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Island</span> <span class="token attr-name">component=</span><span class="token language-javascript"><span class="token punctuation">{</span>Count<span class="token punctuation">}</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Count<span class="token punctuation">"</span></span> <span class="token attr-name">props=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">title</span><span class="token operator">:</span> <span class="token string">'island'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span>Customizing island options<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Island</span> <span class="token attr-name">component=</span><span class="token language-javascript"><span class="token punctuation">{</span>Count<span class="token punctuation">}</span></span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Count<span class="token punctuation">"</span></span> <span class="token attr-name">islandProps=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token string-property property">'on:interaction'</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>To see it in action, check out the <a href="https://sveltekit-is-land.vercel.app/">demo site</a> and scroll - the gray buttons are server-side rendered but not interactive. Once they turn blue, they're hydrated. The page shows off the different hydration modes from is-land, e.g. on load, on visible, and on interaction.</p>
<p>Also, take a look at the network tab to see when the individual component JS is actually loaded. The page only loads 2.49KB of JavaScript (compressed) on initial load, for a total of 9.45KB once all components have been loaded.</p>
<p>Heavy disclaimer: this does <strong>not</strong> mean that partial hydration is coming to Svelte or SvelteKit itself. It's just a userland POC for now, though ideally one that I could package up and make plug-n-play for folks who want to use it.</p>
<p>Honestly, most of the magic here is the existing 11ty/is-land implementation. I just had to figure out how to pass it a component import URL it could use, and then it took over the actual hydration process and timing. It’s a great package, and I love that it’s framework-independent!</p>
<p>There are some <a href="https://github.com/geoffrich/sveltekit-is-land#current-limitations">limitations</a> for island components that I detail in the README:</p>
<ul>
<li>they can't use SvelteKit lib/app/env aliases</li>
<li>only devalue-serializable props and no slots</li>
<li>no client-side routing - once you navigate to a page with CSR enabled, full pages will be hydrated</li>
</ul>
<p>It's also awkward right now that you have to pass both the component and the filename to the Island component at the moment. I want to figure out how to make that more ergonomic.</p>
<p>For a full rundown of how everything works, take a look at the <a href="https://github.com/geoffrich/sveltekit-is-land">project README</a> on GitHub.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>4 things I miss from Svelte after working in React</title>
    <link href="https://geoffrich.net/posts/4-things-i-miss-svelte/"/>
    <updated>2022-10-09T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/4-things-i-miss-svelte/</id>
    
    <content type="html"><![CDATA[
      <p>I got a new job in August, and I'm really enjoying the new problem space and all the great people I work with! However, like most companies, they write their frontend in React. This took some getting used to after writing Svelte for so long. Here are four things from Svelte that I deeply miss when writing React code.</p>
<p>(Psst - this is not a React hate post. Different frameworks solve different problems! At the end of the day users don't care about the syntax of the frameworks we use; they care about the end experience.)</p>
<h2>Scoped styles</h2>
<p>Svelte just makes component styling so easy. You don't have to make a decision about what method to use (styled components? CSS modules? emotion? all global CSS?) and can just write them directly in the component file. Those styles are also <a href="https://svelte.dev/docs#component-format-style">scoped to the component</a> so you know what might break when you change it.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">></span></span>You can style me directly<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token comment">/* I only apply to buttons in this component */</span><br>  <span class="token selector">button</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>Sure, there can be some friction (usually when you want to style something <em>not</em> in the component), but for the vast majority of use cases it's all you need.</p>
<p>Since React doesn't have a blessed styling solution, you need to choose one, and that leads to inconsistency in the codebase as different developers bring their own approach to styles.</p>
<h2>Component prop shorthand</h2>
<p>This is a small one, but I really miss being able to write this...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Button</span> <span class="token language-javascript"><span class="token punctuation">{</span>disabled<span class="token punctuation">}</span></span><span class="token punctuation">></span></span></code></pre>
<p>...instead of this...</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Button</span></span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>disabled<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"></code></pre>
<p>It's the little things.</p>
<h2>Actions</h2>
<p>I love <a href="https://svelte.dev/tutorial/actions">Svelte actions</a>, since they make it easy to tie some behavior to an individual HTML element's lifecycle. I ran into a problem at work that would've been a <em>perfect</em> fit for one (we were adding and removing an iframe and needed to add/remove event listeners and do some setup), but since this wasn't Svelte, it required extra effort.</p>
<p>There's likely a more idiomatic React way of accomplishing this (maybe <a href="https://dev.to/isaachagoel/react-gets-svelte-flavoured-drag-and-drop-or-svelte-actions-via-react-hooks-2pe">a custom hook</a>), and I ended up going with a combination of <code>useEffect</code> and <code>useRef</code>. But a Svelte action would have been <em>so convenient</em>.</p>
<h2>Automatic reactive dependencies</h2>
<p><code>useEffect</code> is a powerful tool, but one annoying thing is that you have to manually list each variable you depend on after the effect.</p>
<pre class="language-js"><code class="language-js"><span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token function">doAThingWith</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var3<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var3<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// why do I have to write this?</span></code></pre>
<p>Not adding all the dependencies (or adding too many) can lead to subtle bugs where the component doesn't behave as expected. The <a href="https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect">React docs</a> say that the dependency list may be generated by a compiler in the future, but for now you need to play spot-the-difference and keep it up-to-date manually.</p>
<p>In Svelte, you don't have to do this. Reactive statements will <a href="https://svelte.dev/docs#component-format-script-3-$-marks-a-statement-as-reactive">automatically detect</a> which variables they depend on and re-run when they change. And because Svelte components don't &quot;re-render&quot; like React components do (the component script tag only runs once), stale closures aren't a concern.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// I will automatically re-run when any of these variables change</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token function">doAThingWith</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var3<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<h2>Wrapping up</h2>
<p>There are some things I appreciate about React - the ecosystem is very mature, and it's nice how composable hooks are. But I won't be switching from Svelte on anything where I have a choice.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Teaching Kelvin Svelte on TKYT</title>
    <link href="https://geoffrich.net/posts/tkyt/"/>
    <updated>2022-09-29T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/tkyt/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="QoR0AZ-Rov8" style="background-image: url('https://i.ytimg.com/vi/QoR0AZ-Rov8/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Learning Svelte from scratch with Geoff Rich: A Svelte tutorial</span>
  </button>
</lite-youtube>
<p>I was on <a href="https://dominuskelvin.dev/tkyt">Teach Kelvin Your Thing</a> to teach Kelvin Svelte! I had a great time — thanks again to Kelvin for having me on.</p>
<p>We created a Star Wars watch order app. You can check out the <a href="https://sw-demo-svelte.vercel.app/">live demo</a> or view the source code <a href="https://github.com/geoffrich/star-wars-demo-svelte">on GitHub</a>. We covered:</p>
<ul>
<li>What Svelte is</li>
<li>Svelte component basics — script, template, style</li>
<li>Reactivity in Svelte</li>
<li>Svelte’s accessibility warnings</li>
<li>Using <code>bind:value</code> to get the value of an input</li>
<li>Reactively sorting and filtering a list</li>
<li>Toggling classes on an element with <code>class:</code></li>
<li>Using <code>&lt;slot&gt;</code></li>
<li>Adding polish with the built-in <code>fade</code> and <code>crossfade</code> transitions and <code>animate:flip</code></li>
</ul>
<p>If you're new to Svelte, I recommend checking out the <a href="https://svelte.dev/tutorial">Svelte tutorial</a>. Be sure to stop by the <a href="https://svelte.dev/chat">Svelte Discord server</a> if you need help!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Page Transitions in SvelteKit with the View Transitions API</title>
    <link href="https://geoffrich.net/posts/page-transitions-1/"/>
    <updated>2022-09-19T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/page-transitions-1/</id>
    
    <content type="html"><![CDATA[
      <div class="callout">
<p>This post was updated in February 2023 to handle breaking changes in the view transitions API (previously called shared element transitions), and in September 2023 with a new <a href="https://svelte.dev/blog/view-transitions">SvelteKit lifecycle method</a> that streamlines the setup process.</p>
</div>
<p>Chrome now supports a new web API that will let you easily animate between two different pages, which is a long-desired feature in browsers.</p>
<p>I’ve been experimenting with how to use this new browser API in <a href="https://kit.svelte.dev">SvelteKit</a>. Today I want to show you what I’ve been doing and how it works. I’ll only scratch the surface of this stuff, and I highly recommend reading <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">the official API explainer</a> if you want to learn more. Today we'll focus on page transitions, but this API can also be used to animate <a href="https://twitter.com/geoffrich_/status/1625897774859587585"><em>any</em> change in DOM state</a>.</p>
<p>This post is an adaptation of (a section of) <a href="/posts/svelte-london-2022/">my talk</a> at the Svelte London meetup last month, though keep in mind that the talk version covers the old API, which has since gone through breaking changes.</p>
<p>Be advised that this API is only available in Chrome or Chromium-based browsers (e.g. Edge) at time of writing, though other browser vendors have expressed interest. It’s still very early, so it might not be ready for your production sites today. However, it <em>is</em> a great candidate for progressive enhancement, since browsers that don't support the API can fall back to a regular navigation.</p>
<p>For the TL;DR, here’s the <a href="https://github.com/geoffrich/sveltekit-view-transitions">repo</a> and the <a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/">deployed demo</a>. Make sure to read the note above about browser requirements.</p>
<p>So: this new API, how does it work?</p>
<h2>How the API works</h2>
<p>At a high level, here is what using the API looks like in a generic single-page app context:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">spaNavigate</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">updateTheDOMSomehow</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>When a single-page app navigation is about to take place, we call <code>document.startViewTransition()</code>, passing a callback that updates the DOM. This could involve adding or removing elements, changing CSS classes or styles, or whatever you like. Once the callback finishes, the browser will transition to the new page state — by default, it does a crossfade between the old and the new states.</p>
<p>Behind the scenes, the browser does something really clever. When the transition starts, it captures the current state of the page and takes a screenshot. It then holds that screenshot in place while the DOM is updating. Once the DOM has finished updating, it captures the new state, and animates between the two states.</p>
<p>We’ll get into more detail about how to interact with this API later in the post, but see the excellent <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">official explainer</a> for much more detail.</p>
<p>So, how do we get this to work in SvelteKit?</p>
<h2>Implementing in SvelteKit</h2>
<p>For SvelteKit, we’ll write a <code>preparePageTransition</code> function that will set everything up for our page transitions. We’ll import that function in our root <code>+layout.svelte</code> so it affects the whole app.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>preparePageTransition<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/page-transition'</span><span class="token punctuation">;</span><br><br>  <span class="token function">preparePageTransition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span></code></pre>
<p>Here's what <code>preparePageTransition</code> looks like:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>onNavigate<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/navigation'</span><span class="token punctuation">;</span><br><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">preparePageTransition</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token comment">// before completing the navigation, start a new transition</span><br>  <span class="token function">onNavigate</span><span class="token punctuation">(</span><span class="token parameter">navigation</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">resolve</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>        <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>        <span class="token keyword">await</span> navigation<span class="token punctuation">.</span>complete<span class="token punctuation">;</span><br>      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Implementing this function used to be somewhat complicated, since there wasn't a good place in SvelteKit's lifecycle to start the view transition. <a href="https://kit.svelte.dev/docs/modules#$app-navigation-beforenavigate">beforeNavigate</a> existed, but it ran <em>before</em> any data fetching started, so a slow API call could force the user to wait multiple seconds for the transition to complete. SvelteKit 1.24 introduced <a href="https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate">onNavigate</a>, which runs just before the navigation completes and is a perfect place to start a view transition.</p>
<p>For those interested, let’s walk through it line by line. If you want to skip to writing some page transitions, jump to <a href="#heading-in-a-sveltekit-app">the next section</a>.</p>
<pre class="language-js"><code class="language-js"><span class="token function">onNavigate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token comment">// ...</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This queues some code to run on every navigation, immediately before the new page is rendered. Importantly, it will run after any data loading for the page has completed – since starting a view transition prevents any interaction with the page, we want to start it as late as possible. See the <a href="https://kit.svelte.dev/docs/modules#$app-navigation-onnavigate">SvelteKit docs</a> for more.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span>startViewTransition<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span></code></pre>
<p>If the browser doesn’t support view transitions, we don’t perform a page transition.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">resolve</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  document<span class="token punctuation">.</span><span class="token function">startViewTransition</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">await</span> navigation<span class="token punctuation">.</span>complete<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>By returning a promise from <code>onNavigate</code>, we suspend the navigation until that promise resolves. This lets us wait to complete the navigation until the view transition has started. We use a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise">promise constructor</a> so that we can control when the promise resolves.</p>
<p>Inside the promise constructor, we start the view transition. Inside the view transition callback we resolve the promise we just returned, which indicates to SvelteKit that it should finish the navigation. It’s important that the navigation waits to finish until after we start the view transition – the browser needs to snapshot the old state so it can transition to the new state.</p>
<p>Finally, inside the view transition callback we wait for SvelteKit to finish the navigation by awaiting the provided <code>navigation.complete</code> promise. Once <code>navigation.complete</code> resolves, the new page has been loaded into the DOM and the browser can animate between the two states.</p>
<p>This isn’t that much code, and you won’t have to interact with it directly. From this point on, you can assume the page transition will happen and set up the page accordingly.</p>
<h2>In a SvelteKit App</h2>
<p>Okay, let’s write some page transitions! If you want to follow along, clone the <code>initial</code> branch of <a href="https://github.com/geoffrich/sveltekit-view-transitions/tree/initial">this repo</a>, which is the demo we’ll be working off of today. It’s based off a <a href="https://codelabs.developers.google.com/create-an-instant-and-seamless-web-app#5">codelab</a> from the Chrome team, which I re-wrote using SvelteKit. You’ll also need Chrome or another Chromium-based browser. This demo also happens to use Tailwind since the codelab I forked included it, though I took it out of the code samples in this post for brevity's sake.</p>
<p>For a preview of what we’re building, check out the <a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/">deployed demo</a> (again, only works in Chromium browsers).</p>
<p>This is a pretty simple app — it displays a list of fruits, and each fruit has its own page with nutrition facts about the fruit.</p>
<p>The first thing to do is to run our <code>preparePageTransition</code> function in our top level <code>+layout.svelte</code>. This will set up the navigation lifecycle hooks so that every navigation will trigger an animated page transition.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token string">'../app.css'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> Navbar <span class="token keyword">from</span> <span class="token string">'$lib/Navbar.svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> Footer <span class="token keyword">from</span> <span class="token string">'$lib/Footer.svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> preparePageTransition <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$lib/page-transition'</span><span class="token punctuation">;</span><br><br>	<span class="token function">preparePageTransition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Navbar</span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Footer</span> <span class="token punctuation">/></span></span></code></pre>
<p>And just like that we have a nice crossfade effect, with no other changes to the app.</p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="lJSgKtDK4Ks" style="background-image: url('https://i.ytimg.com/vi/lJSgKtDK4Ks/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: SvelteKit Page Transitions - Example 1</span>
  </button>
</lite-youtube>
<p>Note that it also works when using the browser’s back and forward buttons.</p>
<p>(No, I'm not sure why the screen jiggles on every navigation.)</p>
<p>Now, one cool thing about this API is that a lot of it is customizable with regular CSS animation. To see how that’s possible, open the <a href="https://developer.chrome.com/docs/devtools/css/animations/#get_started">animations tab in Chrome Devtools</a>. Click the “pause” icon to pause the next animation and trigger a navigation. This will allow you to inspect the <code>::view-transition</code> pseudo elements created by the browser during the transition. You’ll find it at the top of the Elements inspector, right below the <code>&lt;html&gt;</code> element. It looks something like this:</p>
<pre class="language-bash"><code class="language-bash">::view-transition<br>└─ ::view-transition-group<span class="token punctuation">(</span>root<span class="token punctuation">)</span><br>   └─ ::view-transition-image-pair<span class="token punctuation">(</span>root<span class="token punctuation">)</span><br>      ├─ ::view-transition-old<span class="token punctuation">(</span>root<span class="token punctuation">)</span><br>      └─ ::view-transition-new<span class="token punctuation">(</span>root<span class="token punctuation">)</span></code></pre>
<p>Remember how I said the browser takes a screenshot of the current and incoming states? Those pseudo-elements represent those screenshots. You can also see how the default crossfade is applied by inspecting the <code>old</code> and <code>new</code> elements. You’ll see something like the following CSS:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">html::view-transition-old()</span> <span class="token punctuation">{</span><br>  <span class="token property">animation-name</span><span class="token punctuation">:</span> -ua-view-transition-fade-out<span class="token punctuation">;</span><br>  <span class="token property">animation-duration</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br>  <span class="token property">animation-fill-mode</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token selector">html::view-transition-new()</span> <span class="token punctuation">{</span><br>  <span class="token property">animation-name</span><span class="token punctuation">:</span> -ua-view-transition-fade-in<span class="token punctuation">;</span><br>  <span class="token property">animation-duration</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br>  <span class="token property">animation-fill-mode</span><span class="token punctuation">:</span> inherit<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>The default crossfade animation is just a regular CSS animation! This means we can adjust those animations using our <em>own</em> CSS. For instance, you can modify the duration of the transition by setting <code>animation-duration</code>. This will create a really slow fade.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">::view-transition-old(root),<br>::view-transition-new(root)</span> <span class="token punctuation">{</span><br>  <span class="token property">animation-duration</span><span class="token punctuation">:</span> 3s<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Having access to all of CSS animation gives you a lot of power. In the API explainer, they also show <a href="https://developer.chrome.com/docs/web-platform/view-transitions/#simple-customization">how to implement a sliding page transition</a> using only CSS. You can also use native CSS features like media queries to change the animation depending on screen size or other device characteristics.</p>
<p>However, my favorite aspect of this API uses a new CSS property: <code>view-transition-name</code>. With this property, we can animate an element on the old page to its next position on the old page. Let’s look at that next.</p>
<h2>Animating an element from one page to the next</h2>
<p>Okay, now let's implement something a little more app-like. The fruit images on the list and the details pages are conceptually the <em>same element</em>. They just happen to be represented by different HTML elements and appear on different pages. To make that relationship clear, we could animate the image on the list page to the position of the image on the details page the user is navigating to. This is a little hard to explain in text, so skip to the end to watch a video of the final result if you want.</p>
<p>This is something that would've been tricky to do with current browser APIs, but the view transition API makes this possible. To do this, we need to add a <code>view-transition-name</code> to each element. The browser will animate elements with the same <code>view-transition-name</code> from their position on the old page to their position on the new page.</p>
<p>First, go to <code>src/lib/Icon.svelte</code>. This is the component with the fruit image on the list page. Add a CSS rule targeting the image.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> src<span class="token punctuation">;</span><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> name<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span><br>      <span class="token language-javascript"><span class="token punctuation">{</span>src<span class="token punctuation">}</span></span><br>      <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80<span class="token punctuation">"</span></span><br>      <span class="token attr-name"><span class="token namespace">style:</span>height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>80px<span class="token punctuation">"</span></span><br>      <span class="token attr-name">alt="picture</span> <span class="token attr-name">of</span> <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><br>    <span class="token punctuation">/></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">img</span> <span class="token punctuation">{</span><br>    <span class="token property">view-transition-name</span><span class="token punctuation">:</span> fruit<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>We then can give the image in the details page component the same <code>view-transition-name</code>. Go to <code>/src/routes/fruits/[name]/+page.svelte</code> and target that <code>img</code> as well.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> data<span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">,</span> image<span class="token punctuation">,</span> amountPer<span class="token punctuation">,</span> nutrition <span class="token punctuation">}</span> <span class="token operator">=</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">import</span> Nutrition <span class="token keyword">from</span> <span class="token string">'$lib/Nutrition.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Fruits - <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span><br>			<span class="token attr-name">src=</span><span class="token language-javascript"><span class="token punctuation">{</span>image<span class="token punctuation">}</span></span><br>			<span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>240<span class="token punctuation">"</span></span><br>			<span class="token attr-name"><span class="token namespace">style:</span>height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>240px<span class="token punctuation">"</span></span><br>			<span class="token attr-name">alt="picture</span> <span class="token attr-name">of</span> <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><br>		<span class="token punctuation">/></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Nutrition</span> <span class="token language-javascript"><span class="token punctuation">{</span>amountPer<span class="token punctuation">}</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>nutrition<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">img</span> <span class="token punctuation">{</span><br>		<span class="token property">view-transition-name</span><span class="token punctuation">:</span> fruit<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>And that should be all you need to transition between the two! Except… this doesn’t work 😬 If you navigate between the two pages, you’ll see an error in the console:</p>
<pre class="language-bash"><code class="language-bash">Unexpected duplicate view-transition-name: fruit</code></pre>
<p>This is because view transition names <em>need to be unique</em>. Since we applied the tag to the Icon component, every item in the list of fruits has the same view transition name, and the browser doesn’t know which element should be transitioned to its spot on the new page.</p>
<p>(This means this will actually work if we only have one element in the list. You can see this for yourself by commenting out all but one of the fruits in <code>src/routes/fruits/+page.server.js</code>.)</p>
<p>To fix this, we can give each element a tag based on the name of the fruit. So the apple will have <code>view-transition-name: fruit-apple</code>, the banana will have the tag <code>fruit-banana</code>, and so on. However, the fruit’s name is in the component state, and we can’t directly access it using CSS. We need to pass it through a CSS custom property first. First, we set the custom property on the element using a <a href="/posts/style-directives/">style directive</a>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name"><span class="token namespace">style:</span>--tag</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>fruit-{name}<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span></code></pre>
<p>We can then access this variable in CSS:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">img</span> <span class="token punctuation">{</span><br>    <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--tag<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>If you make this change in both of the components, the transition will work! You can add tags to the text in each component so that transitions too.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token comment">// src/lib/ListItem.svelte</span><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> item<span class="token punctuation">;</span><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> href<span class="token punctuation">;</span><br><br>  <span class="token keyword">import</span> Icon <span class="token keyword">from</span> <span class="token string">'$lib/Icon.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token language-javascript"><span class="token punctuation">{</span>href<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Icon</span> <span class="token attr-name">src="</span><span class="token language-javascript"><span class="token punctuation">{</span>item<span class="token punctuation">.</span>image<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token attr-name">name="</span><span class="token language-javascript"><span class="token punctuation">{</span>item<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token punctuation">/></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>--tag="h-</span><span class="token language-javascript"><span class="token punctuation">{</span>item<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>item<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">div</span> <span class="token punctuation">{</span><br>    <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--tag<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token comment">// src/routes/fruits/[name]/+page.svelte</span><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> data<span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">,</span> image<span class="token punctuation">,</span> amountPer<span class="token punctuation">,</span> nutrition <span class="token punctuation">}</span> <span class="token operator">=</span> data<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">import</span> Nutrition <span class="token keyword">from</span> <span class="token string">'$lib/Nutrition.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Fruits - <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>head</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span><br>			<span class="token attr-name">src=</span><span class="token language-javascript"><span class="token punctuation">{</span>image<span class="token punctuation">}</span></span><br>			<span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>240<span class="token punctuation">"</span></span><br>			<span class="token attr-name"><span class="token namespace">style:</span>height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>240px<span class="token punctuation">"</span></span><br>			<span class="token attr-name">alt="picture</span> <span class="token attr-name">of</span> <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><br>			<span class="token attr-name"><span class="token namespace">style:</span>--tag="fruit-</span><span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><br>		<span class="token punctuation">/></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span> <span class="token attr-name"><span class="token namespace">style:</span>--tag="h-</span><span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Nutrition</span> <span class="token language-javascript"><span class="token punctuation">{</span>amountPer<span class="token punctuation">}</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>nutrition<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">h1,<br>	img</span> <span class="token punctuation">{</span><br>		<span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--tag<span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>Here’s what the final product looks like.</p>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="kYpPuJWsOKU" style="background-image: url('https://i.ytimg.com/vi/kYpPuJWsOKU/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: SvelteKit Page Transitions - Example 2</span>
  </button>
</lite-youtube>
<p>Now we have this slick transition and we had to write very little code — the browser does the heavy lifting!</p>
<p>You can reduce some of the duplication by adding a rule to the global styles that targets elements with the <code>--tag</code> variable set. In your <code>app.css</code>, write the following:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* Whenever the style attribute includes "--tag" */</span><br><span class="token selector">[style*='--tag']</span> <span class="token punctuation">{</span><br>  <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--tag<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Now any HTML element that includes <code>--tag</code> in its inline style will automatically apply a <code>view-transition-name</code> and you can remove the styles setting <code>view-transition-name</code> in each component.</p>
<p>We should also wrap this code in a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduced-motion</a> media query, so that the animations with a lot of motion only play for users who haven't requested reduced motion. The default crossfade doesn't involve motion, so we don't need to disable all animation. For more on reduced motion and view transitions, see <a href="https://developer.chrome.com/docs/web-platform/view-transitions/#reacting-to-the-reduced-motion-preference">the explainer</a>.</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* Only do the FLIP-style animations when no reduced-motion preference */</span><br><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> no-preference<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br>  <span class="token selector">[style*='--tag']</span> <span class="token punctuation">{</span><br>    <span class="token property">view-transition-name</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--tag<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>Here’s the final <a href="https://github.com/geoffrich/sveltekit-view-transitions">repo</a> and <a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/">deployed demo</a>.</p>
<h2>Wrapping up</h2>
<p>I’m excited for this API and the features it will unlock, even though it will be a while before it becomes a spec and is implemented cross-browser.</p>
<p>However, that shouldn’t stop you from implementing page transitions in your app today. It’s possible to use Svelte’s built-in transitions to achieve a similar effect. Simple transitions can be accomplished using a <code>#key</code> block and the built-in fade and fly transitions. For more details, see this guide from <a href="https://joshcollinsworth.com/blog/build-static-sveltekit-markdown-blog#implement-page-transitions">Josh Collinsworth</a>. And for some other impressive examples, see these demos from <a href="https://github.com/pngwn/svelte-travel-transitions/">pngwn</a> and <a href="https://github.com/bfanger/page-transitions-in-svelte">Bob Fanger</a>.</p>
<p>For some more examples of Svelte and the View Transition API, see <a href="/posts/view-transition-experiments/">this post</a>.</p>
<h2>Further reading</h2>
<p>Note: some of these resources deal with the old shared-element-transition flavor of the API and are out of date. See the <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">Chrome explainer</a> or the <a href="https://github.com/WICG/view-transitions">draft of the spec</a> for the most up-to-date information.</p>
<ul>
<li><a href="https://youtu.be/JCJUPJ_zDQ4">Google I/O talk</a></li>
<li><a href="https://www.oddbird.net/2022/06/29/shared-elements/">Miriam Suzanne</a></li>
<li><a href="https://www.maxiferreira.com/blog/astro-page-transitions/">Astro and the Shared Element Transition API</a></li>
<li><a href="https://svelte.dev/blog/view-transitions">Unlocking view transitions in SvelteKit 1.24</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte London August 2022</title>
    <link href="https://geoffrich.net/posts/svelte-london-2022/"/>
    <updated>2022-08-23T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-london-2022/</id>
    
    <content type="html"><![CDATA[
      <div class="callout">
<p>Disclaimer: this presentation was about an API that has since been renamed and had some breaking changes. See the <a href="https://developer.chrome.com/docs/web-platform/view-transitions/">official explainer</a> for the most up-to-date information.</p>
</div>
<script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="ua6gE2zPulw" style="background-image: url('https://i.ytimg.com/vi/ua6gE2zPulw/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Svelte London - August Meetup</span>
  </button>
</lite-youtube>
<p>I'm presenting remotely at the Svelte London meetup today. I'll talk about using the new shared element transition API to enable page transitions in a SvelteKit app. Below are some relevant repos and resources for the talk.</p>
<p>Demos:</p>
<ul>
<li><a href="https://github.com/geoffrich/sveltekit-shared-element-transitions">Fruit List</a></li>
<li><a href="https://github.com/geoffrich/http-203-svelte">Svelte Summit Videos</a></li>
</ul>
<p>Further reading:</p>
<ul>
<li><a href="https://developer.chrome.com/blog/shared-element-transitions-for-spas/">Chrome explainer</a></li>
<li><a href="https://youtu.be/JCJUPJ_zDQ4">Google I/O Talk</a></li>
<li><a href="https://github.com/WICG/shared-element-transitions">WICG Proposal</a></li>
<li><a href="https://http203-playlist.netlify.app/">Original HTTP 203 demo</a></li>
<li><a href="https://github.com/sveltejs/kit/issues/5689">Open SvelteKit issue</a></li>
<li><a href="/posts/svelte-summit-2021/">Transitions and accessibility</a></li>
</ul>
<p>Page Transitions in Svelte today</p>
<ul>
<li><a href="https://joshcollinsworth.com/blog/build-static-sveltekit-markdown-blog#implement-page-transitions">Josh Collinsworth</a></li>
<li><a href="https://twitter.com/DanaWoodman/status/1559610048334049280">Dana Woodman</a></li>
<li><a href="https://dev.to/evanwinter/page-transitions-with-svelte-kit-35o6">Simple Page Transitions with SvelteKit</a></li>
<li><a href="https://twitter.com/bfanger/status/1528310176918519809">Another recreation of the HTTP 203 site</a> (<a href="https://github.com/bfanger/page-transitions-in-svelte">source</a>)</li>
<li><a href="https://github.com/pngwn/svelte-travel-transitions/">Svelte Travel Transitions</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Building a Guest Book on the Edge with SvelteKit, Upstash Redis and Vercel</title>
    <link href="https://upstash.com/blog/sveltekit-edge-redis"/>
    <updated>2022-07-28T00:00:00Z</updated>
    <id>https://upstash.com/blog/sveltekit-edge-redis</id>
    
    <summary>Posted on Upstash: Building an app with SvelteKit, Upstash Redis, and Vercel Edge Functions.</summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Some assorted Svelte demos</title>
    <link href="https://geoffrich.net/posts/assorted-svelte-demos/"/>
    <updated>2022-07-06T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/assorted-svelte-demos/</id>
    
    <content type="html"><![CDATA[
      <p>Over the past couple months I’ve been posting some Svelte demos to Twitter. I’ve been meaning to turn some of them into full blog posts, but I’ve been quite busy so that hasn’t happened yet.</p>
<p>Since Twitter is ephemeral, I wanted to write a quick post capturing these demos for future reference. No in-depth explanations here—I’ll be keeping it pretty short. However, some of these might be given a full write-up in the future.</p>
<h2>Using svelte:element to create a reusable wrapper</h2>
<p><a href="https://twitter.com/geoffrich_/status/1525167882396569601">Original tweet</a></p>
<p><a href="https://svelte.dev/docs#template-syntax-svelte-element">svelte:element</a> was released in Svelte 3.47.0, which allows you to dynamically render a given HTML tag. See the <a href="https://svelte.dev/tutorial/svelte-element">official tutorial section</a> for more details. I used it to create a custom <code>&lt;Wrapper&gt;</code> component that can conditionally wrap its children.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> tag <span class="token operator">=</span> <span class="token string">'div'</span><span class="token punctuation">;</span><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> wrap <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> wrap<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>element</span> <span class="token attr-name">this="</span><span class="token language-javascript"><span class="token punctuation">{</span>tag<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">...</span>$$restProps<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token namespace">svelte:</span>element</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>And it can be used like so:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Wrapper</span> <span class="token attr-name">tag</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>details<span class="token punctuation">"</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>wrap<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">></span></span>Yar 🦜 🏴‍☠️<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>    Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace<br>    holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.<br>    Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>    Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable<br>    strike colors. Bring a spring upon her cable holystone blow the man down spanker<br>    Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho<br>    keelhaul squiffy black spot yardarm spyglass sheet transom heave to.<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Wrapper</span><span class="token punctuation">></span></span></code></pre>
<p>Without this component, you’d have to introduce some significant duplication.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> wrap<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>details</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">></span></span>Yar 🦜 🏴‍☠️<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>    Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace<br>    holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl.<br>    Swab barque interloper chantey doubloon starboard grog black jack gangway rutters.<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>details</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>summary</span><span class="token punctuation">></span></span>Yar 🦜 🏴‍☠️<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>summary</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>  Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm. Pinnace<br>  holystone mizzenmast quarter crow's nest nipperkin grog yardarm hempen halter furl. Swab<br>  barque interloper chantey doubloon starboard grog black jack gangway rutters.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>(For the purposes of this demo, I rendered a summary element even when there’s no details. This is not actually valid HTML — you’d probably want to swap it out for an h2 or something when there’s no details.)</p>
<p>There's a <a href="https://svelte.dev/repl/983851f4fb7044e8b5d66a53ca0b356b?version=3.48.0">full demo</a> in the REPL with some more examples.</p>
<h2>Native page transitions in SvelteKit</h2>
<p><a href="https://twitter.com/geoffrich_/status/1534980702785003520">Original tweet</a></p>
<p>I took the experimental page transition API (a.k.a. <a href="https://github.com/WICG/shared-element-transitions">shared element transitions</a>) for a test drive with SvelteKit, and the result was pretty slick. You’ll need Chrome Canary with the <code>chrome://flags/#document-transition</code> flag enabled if you want to try this one out yourself — the original tweet has a video if you don’t want to jump through those hoops. There’s a <a href="https://sveltekit-shared-element-transitions-codelab.vercel.app/fruits">live demo</a> and a <a href="https://github.com/geoffrich/sveltekit-shared-element-transitions">GitHub repo</a> if you want to see how it was accomplished.</p>
<p>I was able to implement it using SvelteKit’s <code>beforeNavigate</code> and <code>afterNavigate</code> hooks in a top-level __layout. It started out as a port of the <a href="https://codelabs.developers.google.com/create-an-instant-and-seamless-web-app#5">shared element transitions Codelab</a> but I added some extra features:</p>
<ul>
<li>transitions to and from the list page (the original only transitioned to the details page, not from it)</li>
<li>transitions when the browser back &amp; forward buttons are clicked</li>
<li>respects reduced motion by not playing the transitions when requested</li>
</ul>
<p>I came back to this recently and <a href="https://github.com/geoffrich/sveltekit-shared-element-transitions/commit/7de6f37bd07f9dd69af2e0bb59e4285f879d2143">refactored it</a> to use a custom navigation store that made the logic a lot easier to follow.</p>
<p>This one will likely get a full write-up sooner rather than later, since I’ll be presenting it at a meetup in a couple weeks.</p>
<h2>Action to detect when focus leaves an element</h2>
<p><a href="https://twitter.com/geoffrich_/status/1537125628327038976">Original tweet</a></p>
<p>I was working on accessibility improvements to the new <a href="https://learn.svelte.dev">learn.svelte.dev</a> site and needed to automatically close a pop-up menu when a user tabbed out of it. Otherwise, the user’s focus would move behind the menu and they couldn’t see where they are on the page. I ended up implementing it as an action. Here’s what it looks like:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">handleFocusLeave</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> cb</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">function</span> <span class="token function">handleFocusIn</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>node<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>document<span class="token punctuation">.</span>activeElement<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><br>  document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'focusin'</span><span class="token punctuation">,</span> handleFocusIn<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token function-variable function">destroy</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      document<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'focusin'</span><span class="token punctuation">,</span> handleFocusIn<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>To see it in action, see <a href="https://svelte.dev/repl/77100d6479594811a833eff2315dd1f4?version=3.48.0">the demo in the Svelte REPL</a> or interact with the tutorial menu on <a href="https://learn.svelte.dev">learn.svelte.dev</a>.</p>
<p>This was inspired by a similar approach from Andy Bell in a <a href="https://piccalil.li/tutorial/build-a-fully-responsive-progressively-enhanced-burger-menu/#:~:text=The%20next%20part%20is%20an,force%20the%20menu%20closed%2C%20immediately.">tutorial on building a burger menu</a>.</p>
<h2>Recursive Svelte action</h2>
<p><a href="https://twitter.com/geoffrich_/status/1542176620303118336">Original tweet</a></p>
<p>Normally, you apply Svelte actions to single HTML elements with <code>use:action</code>. But what if you want to apply the same action to a root node and all of its children? You could manually apply the action to each node, or you could write a higher-order action that recursively does this for you. I was able to implement this using a single action and a MutationObserver to track updates. Here’s what it looks like:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">recurse</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> <span class="token punctuation">{</span>action<span class="token punctuation">,</span> params<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> observed <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> <span class="token function-variable function">act</span> <span class="token operator">=</span> <span class="token parameter">node</span> <span class="token operator">=></span> <span class="token function">action</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> params<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">filterForElements</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">x</span> <span class="token operator">=></span> x<span class="token punctuation">.</span>nodeType <span class="token operator">===</span> Node<span class="token punctuation">.</span><span class="token constant">ELEMENT_NODE</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">const</span> observer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MutationObserver</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">mutations</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    mutations<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">mutation</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      <span class="token function">handleAdditions</span><span class="token punctuation">(</span>mutation<span class="token punctuation">.</span>addedNodes<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      <span class="token function">handleRemovals</span><span class="token punctuation">(</span>mutation<span class="token punctuation">.</span>removedNodes<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">handleAdditions</span><span class="token punctuation">(</span><span class="token parameter">addedNodes</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> node <span class="token keyword">of</span> <span class="token function">filterForElements</span><span class="token punctuation">(</span>addedNodes<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">const</span> cleanup <span class="token operator">=</span> <span class="token function">act</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      observed<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> cleanup<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">function</span> <span class="token function">handleRemovals</span><span class="token punctuation">(</span><span class="token parameter">removedNodes</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> node <span class="token keyword">of</span> <span class="token function">filterForElements</span><span class="token punctuation">(</span>removedNodes<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token keyword">const</span> cleanup <span class="token operator">=</span> observed<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      cleanup<span class="token operator">?.</span>destroy<span class="token operator">?.</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>      observed<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><br><br>  observer<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">childList</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">subtree</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> nodes <span class="token operator">=</span> <span class="token punctuation">[</span>node<span class="token punctuation">]</span><span class="token punctuation">;</span><br>  <span class="token keyword">while</span> <span class="token punctuation">(</span>nodes<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> next <span class="token operator">=</span> nodes<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">const</span> cleanup <span class="token operator">=</span> <span class="token function">act</span><span class="token punctuation">(</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    observed<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>next<span class="token punctuation">,</span> cleanup<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    nodes<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>next<span class="token punctuation">.</span>children<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    <span class="token function-variable function">update</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span><span class="token literal-property property">params</span><span class="token operator">:</span> newParams<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      params <span class="token operator">=</span> newParams<span class="token punctuation">;</span><br>      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>key<span class="token punctuation">,</span> value<span class="token punctuation">]</span> <span class="token keyword">of</span> observed<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>        value<span class="token operator">?.</span><span class="token function">update</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      <span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>See the <a href="https://svelte.dev/repl/b7c95fd6876d4d7382777fa6d1a31117?version=3.48.0">Svelte REPL</a> for an example of it in action. The demo is a little contrived — I’m still not entirely sure how this would be useful. I just thought it was a neat idea and had to get it out of my head.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte Radio Episode 43: The other Rich! Geoff!</title>
    <link href="https://geoffrich.net/posts/svelte-radio/"/>
    <updated>2022-05-12T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-radio/</id>
    
    <content type="html"><![CDATA[
      <p>This week I had the pleasure of guesting on <a href="https://www.svelteradio.com/">Svelte Radio</a>, the best (and only?) Svelte-focused podcast on the web! We talked about what I do at Alaska Airlines, life so far as a Svelte core team member, and the recent Svelte Summit event. You can <a href="https://www.svelteradio.com/episodes/the-other-rich-geoff">listen to the episode</a> on all major podcasting platforms.</p>
<p>Here are some relevant links from the conversation:</p>
<ul>
<li>Alaska Airlines' <a href="https://auro.alaskaair.com/">Auro Design System</a>, built using web components</li>
<li><a href="https://youtu.be/qqj2cBockqE">Svelte Summit Spring 2022</a></li>
<li>My Svelte Summit talk from last year on <a href="https://geoffrich.net/posts/svelte-summit-2021/">Svelte transitions and reduced motion</a></li>
<li>The <a href="https://github.com/sveltejs/svelte/issues/5346">GitHub issue</a> for improved reduced motion support in Svelte</li>
<li>Video game music is great coding music. Lately I've been listening to the <a href="https://youtu.be/tLlperhRLQg">Kirby and the Forgotten Land soundtrack</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Quick tip: style prop defaults</title>
    <link href="https://geoffrich.net/posts/style-prop-defaults/"/>
    <updated>2022-04-26T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/style-prop-defaults/</id>
    
    <content type="html"><![CDATA[
      <p>Svelte has a built-in solution for component theming using <a href="https://svelte.dev/docs#template-syntax-component-directives---style-props">style props</a>. So, you can use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS custom properties</a> in your component styles...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>	I have something important to say.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">p</span> <span class="token punctuation">{</span><br>		<span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>		<span class="token comment">/* Decorative styles */</span><br>		<span class="token property">padding</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span><br>		<span class="token property">max-width</span><span class="token punctuation">:</span> 60ch<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>...and easily set them from outside the component using style props.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>TextBox</span> <span class="token attr-name">--line-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mediumspringgreen<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>TextBox</span><span class="token punctuation">></span></span></code></pre>
<p>But what if you want your style to have a default value? The custom property <code>var</code> syntax takes a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties#custom_property_fallback_values">second argument</a> that sets a fallback if the property is not defined. So, if you wanted the default border color to be <code>darkred</code>, you could do the following.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span><br>  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> darkred<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>However, this can get verbose if you want to use <code>--line-color</code> in multiple places, with the same fallback. If you want to update the default value, you have to do it multiple places!</p>
<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span><br>  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> darkred<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline wavy <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> darkred<span class="token punctuation">)</span> 1px<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>There’s two ways to refactor this to make it less verbose. First, you could introduce another custom property for the default value:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span><br>  <span class="token property">--line-color-default</span><span class="token punctuation">:</span> darkred<span class="token punctuation">;</span><br>  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--line-color-default<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline wavy <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--line-color-default<span class="token punctuation">)</span><span class="token punctuation">)</span> 1px<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>This makes it so there’s one place to <em>change</em> the default value, but you still have to provide the second argument to <code>var</code> every time you reference <code>--line-color</code>.</p>
<p>Instead, my recommended approach would be to introduce another custom property that represents <em>either line-color or the fallback</em>.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">p</span> <span class="token punctuation">{</span><br>  <span class="token property">--_line-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--line-color<span class="token punctuation">,</span> darkred<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token property">border</span><span class="token punctuation">:</span> 2px solid <span class="token function">var</span><span class="token punctuation">(</span>--_line-color<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token property">text-decoration</span><span class="token punctuation">:</span> underline wavy <span class="token function">var</span><span class="token punctuation">(</span>--_line-color<span class="token punctuation">)</span> 1px<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>So now you have two variables:</p>
<ul>
<li><code>--line-color</code> is the user-supplied theme value</li>
<li><code>--_line-color</code> is what we use in our styles, and will either be the user-supplied value (if defined) or the default color</li>
</ul>
<p>You can see this in action in this <a href="https://svelte.dev/repl/c14650e187bb48e9a3e168b9955268ea?version=3.47.0">Svelte REPL</a>.</p>
<p>You only need to introduce a variable like <code>--_line-color</code> if you plan on using the theme variable multiple places. Otherwise, it’s perfectly fine to set the fallback where you use the property, as in the first example.</p>
<p>The technique on display here is not unique to Svelte, and can be applied anywhere you use custom properties. However, it's of particular interest with Svelte, since custom properties are the recommended way of theming a component.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Talking Svelte and open source on 20minJS</title>
    <link href="https://geoffrich.net/posts/20minjs/"/>
    <updated>2022-04-13T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/20minjs/</id>
    
    <content type="html"><![CDATA[
      <p>I recently had my first podcast appearance on <a href="https://podcast.20minjs.com">20minJS</a>, where I discussed Svelte and getting into open source with Fernando Doglio. I thought it was a great conversation and really appreciated the opportunity!</p>
<p>I pulled some of my favorite portions of the interview below, though definitely check out the <a href="https://podcast.20minjs.com/1952066/10417700-episode-6-svelte-and-contributing-to-open-source-with-geoff-rich">full episode</a> for the rest of the conversation. It should be available on all major podcast platforms. For more on Svelte, check out an <a href="https://podcast.20minjs.com/1952066/10252768-episode-2-svelte-and-sveltekit-with-mark-volkmann">earlier episode</a> with Mark Volkmann, where they focus more on SvelteKit.</p>
<hr>
<p><em>Describe Svelte in 2-3 sentences.</em></p>
<p>So Svelte is a component-based JavaScript framework like the ones you've probably heard of like React and Vue, but the major difference is instead of interpreting your component code with a runtime it ships to the browser, it instead compiles your components into vanilla JavaScript at build time. So on average, this makes for applications that are typically smaller and faster than applications built with the other big frameworks.</p>
<p><em>Would you recommend a new developer, going directly to Svelte without jumping through React or Vue, or anything else that's out there already?</em></p>
<p>If your only goal is to get a job as quickly as possible, React definitely has way more jobs. [React] is going to be way more marketable, but I don't think you're going to go wrong starting with Svelte. As I mentioned, it's very approachable, it has very minimal boilerplate, it has a very easy mental model to grasp what's going on, and it has a great tutorial... And I think with Svelte, you're going to be productive quicker than if you start with React. And if you start with Svelte, that that doesn't mean you can't go learn React later... But yeah, I would say that understandability, the fact that batteries are included, you're not going to have to research a bunch of external libraries off-the-bat makes it a great choice to start with. And it will grow with you if you want it to, but if you want to move on to React afterwards, that's definitely an option too. It all depends on what your goals are.</p>
<p><em>If I have a JavaScript external library, that I want to include, because it already does what I want it to do, do I have to like migrate it or just include it and will it work out of the box?</em></p>
<p>There's a concept called Svelte actions, which basically let you get that raw DOM access. And it's often a great way to integrate an external library. A common example is like a tool tip library like Tippy. You do have to write a Svelte wrapper, but it's like three lines of code. You have a function that says, hey, run this when this DOM node is added, and then you can import the library and just do whatever you need to with that DOM node... So I found it really easy to integrate external libraries like that... Because there's these external [vanilla JS] libraries that are usually very easy to integrate into Svelte, the ecosystem can actually be a lot larger than maybe something like React where a lot of external libraries you have to figure out—okay, how do I integrate this into React's lifecycle and components?</p>
<div class="callout">
<p>For more on integrating external libraries with Svelte actions, see my LogRocket post <a href="https://blog.logrocket.com/svelte-actions-introduction/">&quot;Introduction to Svelte Actions&quot;</a>.</p>
</div>
<p><em>What is the process of contributing to an open-source project?</em></p>
<p>The first thing you want to do is be familiar with the project. Really it's best if it's a project you've used before—you understand what it's trying to do, what it's goals are. Then I would just check out the GitHub repo and start seeing what kind of issues are open, see how maintainers respond to them, how they respond to PRs and just try to understand norms in the community. And then it's really figuring out how you want to contribute to it. And this doesn't have to be code, it can be writing up a good issue report. It can be trying to improve the docs. Maybe there's something that was unclear to you at first that you want to improve and make better. It could be opening a PR, or it could just being helpful in the community, answering people's questions on the Discord or on Stack Overflow.</p>
<p>If you do actually want to make a PR, there's usually contributing documentation in the repo saying what you need to do. And definitely read that. Definitely read any issue or PR templates as you make those, because it can be really frustrating as a maintainer to have someone open an issue where they didn't provide a good reproduction, so there's nothing you can do. So yeah, just refer to the repo's documentation and go from there.</p>
<p><em>Would you recommend a beginner to get into open source already or would you tell them to wait a bit?</em></p>
<p>The cool thing about open source is that it's a great way to explore and learn. If you're a beginner and you're just starting out, a lot of times, you're super familiar with how you do things or maybe how your company does things in their projects. But open source is just this whole other world. You can see other conventions and how other people write code that can really broaden your perspective. And it's really an opportunity to connect with people and projects that will probably outlive your current company. So I'd say, yeah, dive right in and just do what you can. You might not be implementing a huge new feature right off the bat, but you can definitely be helpful.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>4 tips for cleaner Svelte components</title>
    <link href="https://geoffrich.net/posts/clean-component-tips/"/>
    <updated>2022-04-04T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/clean-component-tips/</id>
    
    <content type="html"><![CDATA[
      <p>I've helped a few devs at my company get started with Svelte, and I love seeing how easy it is for them to pick it up and start being productive. However, I've noticed a few areas where they write verbose code without realizing that Svelte has a cleaner way to do the same thing.</p>
<p>Drawing on that experience, I've collected four ways to write cleaner and Svelte-ier component code. I think these will be especially helpful if you're new to Svelte, though more experienced devs might learn something too.</p>
<h2>Work with the component template, not against it</h2>
<p>When people are new to Svelte (or front-end frameworks in general), sometimes I see them use <code>querySelector</code> in their component code like this:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">function</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> submitButton <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">'.submit-button'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    submitButton<span class="token punctuation">.</span>disabled <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>    submitButton<span class="token punctuation">.</span>innerText <span class="token operator">=</span> <span class="token string">'Submitting...'</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit-button<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">on:</span>click="</span><span class="token language-javascript"><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span><span class="token attr-name">"</span><span class="token punctuation">></span></span><br>  Submit<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>Does it work? Sure. Is it a good idea? <strong>Absolutely not.</strong></p>
<p>I <em>think</em> this happens because devs see the script tag and assume they should treat it like an inline script in a regular HTML document. But the script block in Svelte isn't just a script tag—it's a script tag with superpowers. You aren't limited to how you would interact with elements in vanilla JS.</p>
<p>The benefit of using Svelte (and other modern frameworks) is that your template can be driven by your component state. Instead of manually querying for elements, you can instead express that logic directly in your template. Here's how I would rewrite the first example to consolidate the logic inside the template:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> submitting <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">handleSubmit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    submitting <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click="</span><span class="token language-javascript"><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token attr-name">disabled="</span><span class="token language-javascript"><span class="token punctuation">{</span>submitting<span class="token punctuation">}</span></span><span class="token attr-name">"</span><span class="token punctuation">></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>submitting <span class="token operator">?</span> <span class="token string">'Submitting...'</span> <span class="token operator">:</span> <span class="token string">'Submit'</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>This makes your components much easier to reason about. You can look at the template and understand all the different states a component can be in, instead of trying to track down all the places where you might be updating something in the template.</p>
<p>(In addition, by writing <code>document.querySelector</code> you could potentially retrieve an element in a completely different component than what you're working in. Sometimes that's what you want, but usually it's not.)</p>
<p>Because of this, it's a good rule of thumb to avoid using <code>querySelector</code>, <code>querySelectorAll</code>, or any imperative query methods in your Svelte component. This isn't to say that you should <em>never</em> use them. However, using a feature built-in to Svelte would often be a better solution:</p>
<ul>
<li>If you're updating text or attributes of an element inside your component, you should express it in your component template instead of querying for the element.</li>
<li>If you need a reference to a particular DOM element in your component (which is especially common when integrating vanilla JS libraries), you should first try using <code>bind:this</code> or an action.</li>
</ul>
<p>Only once you've considered and ruled out these methods should you reach for <code>querySelector</code>.</p>
<h2>Two-way binding is awesome</h2>
<p>If you come to Svelte from React, you might instinctively write your form inputs this way:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> value <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">handleInput</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    value <span class="token operator">=</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name">value="</span><span class="token language-javascript"><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token attr-name"><span class="token namespace">on:</span>input="</span><span class="token language-javascript"><span class="token punctuation">{</span>handleInput<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>  The value is <span class="token language-javascript"><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>This works, but is considered by many Svelte devs to be too much boilerplate. In Svelte, you can remove a lot of this code by using <code>bind:value</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> value <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Name<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>value</span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>  The value is <span class="token language-javascript"><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>This can significantly reduce the amount of code you write, especially if you have a lot of form inputs. And <a href="https://svelte.dev/blog/write-less-code">less code means fewer bugs.</a></p>
<p>You can also use two-way binding <a href="https://svelte.dev/docs#template-syntax-component-directives-bind-property">with component props</a>. You need to be more careful with this kind of binding, since overusing it can make it hard to understand where state is being changed in your application. This is especially true if you use two-way binding across multiple layers of components, since any component in that tree could change the state for every component that's bound to it. But for form elements (and components that wrap form elements), two-way binding is the right choice.</p>
<p>For another perspective on this, see <a href="https://imfeld.dev/writing/how_svelte_makes_two_way_binding_safe">&quot;How Svelte Makes Two-Way Binding Safe”</a> by Daniel Imfeld.</p>
<h2>Scoped styles let you write slimmer styles</h2>
<p>In Svelte, styles are <a href="https://svelte.dev/tutorial/styling">automatically scoped</a>. You can write styles in a component and be assured that they won't leak out and unexpectedly affect another part of the application.</p>
<p>If you're used to working in projects without component-scoped styles, you might be used to writing lengthy class names to ensure that they don't conflict with styles in another part of the application. But in Svelte, you don't need to worry about that!</p>
<p>It's perfectly okay to use shorter, more generic class names like “heading” or “wrapper” in your Svelte component, since you can be confident that those styles won't accidentally apply to an element somewhere else. You can even target HTML tag names like <code>p</code> and <code>button</code> safely, though whether you should do this depends on the component.</p>
<p>For example, in the following example, I don't need to add a class to the button and paragraph just to style them. Since I want to style all paragraphs and buttons <em>in this component</em>, I can just target <code>p</code> and <code>button</code> directly, and elements outside this component won't be affected.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>	Katamari Damacy (lit. 'Clump Spirit') is a<br>	third-person puzzle-action video game developed<br>	and	published by Namco for the PlayStation 2.<br>	It was released in Japan in March 2004 and in North<br>	America in September 2004.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">></span></span><br>	That's neat!<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">p</span> <span class="token punctuation">{</span><br>		<span class="token property">border-bottom</span><span class="token punctuation">:</span> 2px solid limegreen<span class="token punctuation">;</span><br>		<span class="token property">padding</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br><br>	<span class="token selector">button</span> <span class="token punctuation">{</span><br>		<span class="token property">color</span><span class="token punctuation">:</span> hotpink<span class="token punctuation">;</span><br>		<span class="token property">font-size</span><span class="token punctuation">:</span> 20px<span class="token punctuation">;</span><br>		<span class="token property">font-weight</span><span class="token punctuation">:</span> bold<span class="token punctuation">;</span><br>		<span class="token property">background-color</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>If you're interested in how this scoping works, I did a deep dive on <a href="https://geoffrich.net/posts/svelte-scoped-styles/">how Svelte scopes styles</a> last year.</p>
<h2>Don't miss out on all of Svelte's syntax sugar</h2>
<p>Svelte has a lot of built-in niceties to reduce boilerplate for common tasks. Here are some that you might have overlooked:</p>
<ul>
<li>Instead of doing manual string interpolation to add and remove classes or set inline styles, you can use <a href="https://svelte.dev/tutorial/classes">class:</a> and <a href="https://geoffrich.net/posts/style-directives/">style:</a>.</li>
<li>Instead of setting up an <code>input</code> listener and keeping local state in sync, you can use <a href="https://svelte.dev/tutorial/text-inputs">bind:value</a>. Similarly, instead of wiring up a <code>change</code> listener to a group of radio buttons, you can use <a href="https://svelte.dev/tutorial/group-inputs">bind:group</a>.</li>
<li>Instead of writing the same expression multiple times inside an <code>#each</code> block, you can use <a href="https://geoffrich.net/posts/local-constants/">@const</a></li>
<li>Instead of <a href="https://svelte.dev/tutorial/bind-this">bind:this and onMount</a> when you need a reference to a particular DOM node, you can <a href="https://blog.logrocket.com/svelte-actions-introduction/">use an action</a></li>
</ul>
<p>Either option will functionally produce the same result, but the second involves less code and more readable components (especially if you can get rid of tricky string interpolation).</p>
<p>I recommend giving the <a href="https://svelte.dev/docs">Svelte docs</a> a full read-through, especially if it's been a while since you went through the tutorial. You might not know what you're missing!</p>
<h2>Wrapping up</h2>
<p>It was fun to do a “quick hits” style of article for this. There's a few more tips I'm chewing over, so you might see a follow-up article at some point.</p>
<p>Thanks to everyone who responded to my “common Svelte antipatterns” <a href="https://twitter.com/geoffrich_/status/1488955388808421380">thread on Twitter</a> (that also somehow devolved into a 🐫 case vs. 🐍 case debate). Go check that out for some other folks' ideas on what makes for cleaner Svelte components!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Local constants in Svelte with the @const tag</title>
    <link href="https://geoffrich.net/posts/local-constants/"/>
    <updated>2022-03-06T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/local-constants/</id>
    
    <content type="html"><![CDATA[
      <p><a href="https://geoffrich.net/posts/style-directives/">Style directives</a> weren't the only new feature introduced in Svelte 3.46! Let's take a look at the other recent addition to Svelte: <a href="https://svelte.dev/docs#template-syntax-const">the @const tag</a>.</p>
<h2>The problem</h2>
<p>Let's say you're displaying a list of boxes and calculating their areas, and also want to apply some styling when the area is a certain amount. You might think of doing something like this:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> boxes <span class="token operator">=</span> <span class="token punctuation">[</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">}</span><br>  <span class="token punctuation">]</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">class:</span>big</span><span class="token attr-value"><span class="token punctuation">=</span>{box.width</span> <span class="token attr-name">*</span> <span class="token attr-name">box.height</span> <span class="token punctuation">></span></span> 10}><br>    <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.big</span> <span class="token punctuation">{</span><br>    <span class="token property">font-size</span><span class="token punctuation">:</span> 2rem<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>Note that we compute <code>box.width * box.height</code> twice — once to display it, and once in the <code>class:big</code> directive. Even though the value hasn't changed, the browser still has to compute it twice. While this isn't an issue with a simple calculation like this, it could impact performance if the calculation was more intensive. It also introduces duplication into the code. If you needed to use the area more times (e.g. to apply different CSS classes), it would further compound these issues.</p>
<p>(As an aside, this is only a problem because we're inside an #each block. If there was only a single box, we could compute the area once in the script block and be done with it.)</p>
<p>Before the const tag was introduced, there were a few ways to work around this issue. You could create a helper function to compute the value...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> boxes <span class="token operator">=</span> <span class="token punctuation">[</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">}</span><br>  <span class="token punctuation">]</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">area</span><span class="token punctuation">(</span><span class="token parameter">box</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">class:</span>big</span><span class="token attr-value"><span class="token punctuation">=</span>{area(box)</span> <span class="token punctuation">></span></span> 10}><br>    <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span><span class="token function">area</span><span class="token punctuation">(</span>box<span class="token punctuation">)</span><span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>This reduces the duplication, but it will still perform the computation multiple times unless you implement some form of <a href="https://kyleshevlin.com/memoization">memoization</a>. Again, this is likely not a concern for a simple calculation like area, but it would be for more expensive calculations.</p>
<p>You could also create a new array that pre-computes the property you want...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> boxes <span class="token operator">=</span> <span class="token punctuation">[</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">}</span><br>  <span class="token punctuation">]</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> mappedBoxes <span class="token operator">=</span> boxes<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">b</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> <span class="token punctuation">{</span><br>      <span class="token operator">...</span>b<span class="token punctuation">,</span><br>      <span class="token literal-property property">area</span><span class="token operator">:</span> b<span class="token punctuation">.</span>width <span class="token operator">*</span> b<span class="token punctuation">.</span>height<br>    <span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">mappedBoxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">class:</span>big</span><span class="token attr-value"><span class="token punctuation">=</span>{box.area</span><span class="token punctuation">></span></span> 10 }><br>    <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>area<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>This works, but feels a little awkward, and now you have to loop over the array multiple times. In a large component, you'd also have to jump between the template where the variable is used and the script where it's defined when making changes.</p>
<p>One final option is to extract a new component...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> Box <span class="token keyword">from</span> <span class="token string">'./Box.svelte'</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> boxes <span class="token operator">=</span> <span class="token punctuation">[</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">2.5</span><span class="token punctuation">}</span><span class="token punctuation">,</span><br>    <span class="token punctuation">{</span><span class="token literal-property property">width</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token literal-property property">height</span><span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">}</span><br>  <span class="token punctuation">]</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Box</span> <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Box</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><br><span class="token comment">&lt;!-- Box.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> box<span class="token punctuation">;</span><br><br>  <span class="token literal-property property">$</span><span class="token operator">:</span> area <span class="token operator">=</span> box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">class:</span>big</span><span class="token attr-value"><span class="token punctuation">=</span>{area</span> <span class="token punctuation">></span></span> 10}><br>  <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>area<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>... but this seems like overkill for such a simple use-case.</p>
<p>Before Svelte 3.46, you would need to choose one of these options. Now, there's an additional solution: <a href="https://svelte.dev/docs#template-syntax-const">local constants</a>.</p>
<h2>The solution: local constants</h2>
<p>Instead of adding logic to the script block, you can declare a constant directly in the markup itself with <code>@const</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>@<span class="token keyword">const</span> area <span class="token operator">=</span> box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">class:</span>big</span><span class="token attr-value"><span class="token punctuation">=</span>{area</span> <span class="token punctuation">></span></span> 10}><br>    <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>area<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>This is more readable, since the value is declared directly where it is used, and more efficient, since it only computes the value once.</p>
<p>The name “const” was chosen because it behaves like a constant: it is read-only and can't be assigned to. Also, like the native JavaScript <code>const</code>, it is scoped to the block it was declared in. The following template produces compiler errors:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>@<span class="token keyword">const</span> area <span class="token operator">=</span> box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span><br>	<span class="token comment">&lt;!-- Error: 'area' is declared using {@const ...} and is read-only --></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">on:</span>hover=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> area <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>		<span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>area<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><span class="token comment">&lt;!-- 'area' is not defined --></span><br><span class="token language-javascript"><span class="token punctuation">{</span>area<span class="token punctuation">}</span></span></code></pre>
<p>Despite the similarity to the JavaScript keyword, there is no corresponding <code>let</code> or <code>var</code> tag. Also, unlike <code>const</code> in JavaScript, variables declared with <code>@const</code> can be used before they are declared. The following example is valid, despite <code>area</code> being used before it's declared with <code>@const</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>    <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>area<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>@<span class="token keyword">const</span> area <span class="token operator">=</span> box<span class="token punctuation">.</span>width <span class="token operator">*</span> box<span class="token punctuation">.</span>height<span class="token punctuation">}</span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<h2>Destructuring inside #each</h2>
<p><code>@const</code> will also make it easier to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">destructure</a> objects inside #each blocks. Currently, you can destructure a variable inside an #each block like this:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript"><span class="token punctuation">{</span>width<span class="token punctuation">,</span> height<span class="token punctuation">}</span><span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>width <span class="token operator">*</span> height<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>However, once you do that, you don't have a reference to the original object any more. If you want to use the original object (e.g. to pass to another component), you need to recreate it.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript"><span class="token punctuation">{</span>width<span class="token punctuation">,</span> height<span class="token punctuation">}</span><span class="token punctuation">}</span></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>width <span class="token operator">*</span> height<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Box</span> <span class="token attr-name">box=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span>width<span class="token punctuation">,</span> height<span class="token punctuation">}</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>If properties are added or removed from the original object, you need to keep this second object up-to-date as well. This can be easy to forget.</p>
<p>Now you can destructure the object with <code>@const</code>, while keeping a reference to the original object.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token language-javascript"><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> box<span class="token punctuation">}</span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><span class="token language-javascript"><span class="token punctuation">{</span>width<span class="token punctuation">}</span></span> * <span class="token language-javascript"><span class="token punctuation">{</span>height<span class="token punctuation">}</span></span> = <span class="token language-javascript"><span class="token punctuation">{</span>width <span class="token operator">*</span> height<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Box</span> <span class="token attr-name">box=</span><span class="token language-javascript"><span class="token punctuation">{</span>box<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>It takes an extra line, but it means that you don't need to introduce a duplicate object.</p>
<h2>Improving readability</h2>
<p>Using <code>@const</code> can also improve the readability of your code by letting you name a variable for what would otherwise be an inline expression. For example:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- Option 1: long, complex inline expression --></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> box<span class="token punctuation">.</span>width <span class="token operator">&lt;</span> <span class="token number">30</span> <span class="token operator">&amp;&amp;</span> box<span class="token punctuation">.</span>width <span class="token operator">></span> <span class="token number">10</span> <span class="token operator">&amp;&amp;</span> box<span class="token punctuation">.</span>height <span class="token operator">%</span> <span class="token number">3</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">}</span></span><br>  <span class="token comment">&lt;!-- Do some conditional rendering... --></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span><br><br><span class="token comment">&lt;!-- Option 2: extract into a local constant --></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">#each</span> <span class="token language-javascript">boxes </span><span class="token keyword">as</span> <span class="token language-javascript">box<span class="token punctuation">}</span></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>@<span class="token keyword">const</span> boxFitsTheRoom <span class="token operator">=</span> box<span class="token punctuation">.</span>width <span class="token operator">&lt;</span> <span class="token number">30</span> <span class="token operator">&amp;&amp;</span> box<span class="token punctuation">.</span>width <span class="token operator">></span> <span class="token number">10</span> <span class="token operator">&amp;&amp;</span> box<span class="token punctuation">.</span>height <span class="token operator">%</span> <span class="token number">3</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">}</span></span><br>  <span class="token comment">&lt;!-- The expression is named, which can help<br>    others understand the purpose of this code --></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> boxFitsTheRoom<span class="token punctuation">}</span></span><br>  <span class="token comment">&lt;!-- Do some conditional rendering... --></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br><span class="token each"><span class="token punctuation">{</span><span class="token keyword">/each</span><span class="token punctuation">}</span></span></code></pre>
<p>While there's no need to do this for <em>every</em> if statement, it can make your code much more understandable when you have lengthy inline expressions.</p>
<h2>Limitations</h2>
<p>The new tag does have a few limitations.</p>
<p><strong>Only allowed in certain contexts</strong>: <code>@const</code> is only allowed as a direct child of <code>{#each}</code>, <code>{:then}</code>, <code>{:catch}</code>, <code>&lt;Component /&gt;</code> or <code>&lt;svelte:fragment /&gt;</code>. These are all block types where a new scope is created. You can't use it by itself at the top level of a template or inside an <code>{#if}</code> / <code>{:else}</code> block, though the latter does have an <a href="https://github.com/sveltejs/svelte/issues/7241">open feature request</a>.</p>
<p><strong>Doesn't support non-standard JavaScript:</strong> because JavaScript expressions inside the markup section of a Svelte component are <a href="https://github.com/sveltejs/svelte/issues/4701">not preprocessed</a>, you won't be able to write expressions in a const tag that use non-standard JavaScript (e.g. TypeScript or syntax that requires Babel plugins).</p>
<p>Also note that at time of writing there are <strong>still some open bugs</strong> around this feature:</p>
<ul>
<li>Fix rvalue error when using arrow functions in {@const} <a href="https://github.com/sveltejs/svelte/issues/7206">#7206</a></li>
<li>@const declaration inside components ignored <a href="https://github.com/sveltejs/svelte/issues/7189">#7189</a></li>
<li>Unclear error message comes if const is already declared in &lt;script&gt; section <a href="https://github.com/sveltejs/svelte/issues/7221">#7221</a></li>
<li>Object property access in callbacks within @const statements are being treated as separate variables <a href="https://github.com/sveltejs/svelte/issues/7326">#7326</a></li>
</ul>
<h2>Wrapping up</h2>
<p>I've already found <code>@const</code> to be <a href="https://github.com/geoffrich/generative-svg-grid/blob/0e3384cebfd42935f687ce8c69a0e495e19d6a1b/src/lib/Grid.svelte#L45-L57">very useful</a> to improve the readability of my code. Keep it in mind next time you're writing Svelte!</p>
<p>The <a href="https://github.com/sveltejs/rfcs/pull/33">original RFC</a> is well worth a read for some different perspectives on whether introducing this tag is a good idea or not. It's also interesting in context of RFCs <a href="https://github.com/sveltejs/rfcs/pull/32">#32</a> (local &lt;style scoped&gt;) and <a href="https://github.com/sveltejs/rfcs/pull/34">#34</a> (inline components). Taken together, these RFCs would allow including multiple components inside a single file. However, at time of writing, only #33 (local constants) has been accepted.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>How to use Svelte&#39;s style directive</title>
    <link href="https://geoffrich.net/posts/style-directives/"/>
    <updated>2022-01-30T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/style-directives/</id>
    
    <content type="html"><![CDATA[
      <p>Svelte 3.46 released a new feature: <a href="https://svelte.dev/docs#template-syntax-element-directives-style-property">style directives</a>! In this post I'll show you how to use them and the advantages they have over setting the <code>style</code> attribute directly.</p>
<h2>The style attribute</h2>
<p>A common strategy for writing dynamic styles in Svelte is to apply the styles directly as an attribute. For example, here's how you can make a <a href="https://svelte.dev/repl/4c7b90f3872b417f9bbf78cfd4570e8e?version=3.46.3">box move around</a> the screen with a couple of range sliders.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> xPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> yPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>positionX<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>X <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>positionX<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>range<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>value="</span><span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>positionY<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Y <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>positionY<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>range<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>value="</span><span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token punctuation">/></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style="left:</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span><span class="token attr-name">%;</span> <span class="token attr-name"><span class="token namespace">top:</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span><span class="token attr-name">%"</span><span class="token punctuation">></span></span><br>  (<span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span>, <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span>)<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.box</span> <span class="token punctuation">{</span><br>    <span class="token property">height</span><span class="token punctuation">:</span> 80px<span class="token punctuation">;</span><br>    <span class="token property">width</span><span class="token punctuation">:</span> 80px<span class="token punctuation">;</span><br>    <span class="token property">background</span><span class="token punctuation">:</span> pink<span class="token punctuation">;</span><br>    <span class="token property">position</span><span class="token punctuation">:</span> absolute<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>The key part here is <code>style=&quot;left: {xPos}%; top: {yPos}%&quot;</code> on the div, which takes the <code>xPos</code> and <code>yPos</code> state variables and sets the element's styles accordingly.</p>
<p>This works well, but can get awkward as you add more properties, especially if those properties are conditionally applied. For instance, look at the following example from the <a href="https://github.com/sveltejs/rfcs/blob/master/text/0008-style-directives.md#motivation">style directive RFC</a>:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>  <span class="token attr-name">style="</span><br>    <span class="token attr-name"><span class="token namespace">position:</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>position<span class="token punctuation">}</span></span><span class="token attr-name">;</span><br>    <span class="token language-javascript"><span class="token punctuation">{</span>position <span class="token operator">===</span> <span class="token string">'absolute'</span> <span class="token operator">?</span> <span class="token string">'top: 20px;'</span> <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">}</span></span><br>    <span class="token language-javascript"><span class="token punctuation">{</span>pointerEvents <span class="token operator">===</span> <span class="token boolean">false</span> <span class="token operator">?</span> <span class="token string">'pointer-events: none;'</span> <span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">}</span></span><br>  <span class="token attr-name">"</span><br><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>It would be easy to forget a semicolon or mishandle a ternary statement and break your dynamic styles. Enter: style directives! 🕺</p>
<h2>Style directives</h2>
<p>In Svelte, a <em>style directive</em> is an attribute applied to an element in the format <code>style:property={value}</code>, where <em>property</em> is a CSS property name and <em>value</em> is the value of that property. By using style directives, you don't need to worry about properly formatting the CSS string, since you set individual properties instead. If you're using Svelte 3.46 or later, the above example can be rewritten like so to use style directives:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><br>  <span class="token attr-name"><span class="token namespace">style:</span>position</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>absolute<span class="token punctuation">"</span></span><br>  <span class="token attr-name"><span class="token namespace">style:</span>top=</span><span class="token language-javascript"><span class="token punctuation">{</span>position <span class="token operator">===</span> <span class="token string">'absolute'</span> <span class="token operator">?</span> <span class="token string">'20px'</span> <span class="token operator">:</span> <span class="token keyword">null</span><span class="token punctuation">}</span></span><br>  <span class="token attr-name"><span class="token namespace">style:</span>pointer-events=</span><span class="token language-javascript"><span class="token punctuation">{</span>pointerEvents <span class="token operator">?</span> <span class="token keyword">null</span> <span class="token operator">:</span> <span class="token string">'none'</span><span class="token punctuation">}</span></span><br><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>And the sliding box example can be written like this:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style:</span>left="</span><span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span><span class="token attr-name">%"</span> <span class="token attr-name"><span class="token namespace">style:</span>top="</span><span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span><span class="token attr-name">%"</span><span class="token punctuation">></span></span><br>  (<span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span>, <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span>)<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>This is about the same amount of code, but is easier to understand and less prone to errors from writing an invalid CSS string.</p>
<p>You can use style directives with <em>any</em> CSS property, including CSS custom property definitions.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>--super-cool-custom-property</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>orange<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>There's also a shorter syntax available if your variable has the same name as the CSS property you're setting. The below two are equivalent:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>color=</span><span class="token language-javascript"><span class="token punctuation">{</span>color<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>color</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>In the case when the <code>style</code> attribute and style directive set the same properties, the style directive will take precedence.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>margin-top: 1rem; color: red<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style:</span>color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>blue<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  I will have 1rem top margin and my color is blue.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<h2>Style optimizations</h2>
<p>By using style directives, you also make sure that Svelte will update the element's styles in an optimal way. This was also possible with the <code>style</code> attribute, but it was easy to accidentally opt-out of the optimization.</p>
<p>Let's look again at the sliding box example.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style="left:</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span><span class="token attr-name">%;</span> <span class="token attr-name"><span class="token namespace">top:</span></span> <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span><span class="token attr-name">%"</span><span class="token punctuation">></span></span><br>  (<span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span>, <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span>)<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>When you compile this component, it turns into two calls to <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/setProperty">setProperty</a>:</p>
<pre class="language-js"><code class="language-js">div<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span><span class="token string">'left'</span><span class="token punctuation">,</span> xPos <span class="token operator">+</span> <span class="token string">'%'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>div<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span><span class="token string">'top'</span><span class="token punctuation">,</span> yPos <span class="token operator">+</span> <span class="token string">'%'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>(If you're ever curious what your Svelte component code turns into, the &quot;JS output&quot; tab of the <a href="https://svelte.dev/repl">REPL</a> is a great place to start.)</p>
<p>Svelte performs some clever optimization here and will <em>only</em> set the property that changes. So, if only <code>xPos</code> changes, Svelte will only call <code>setProperty('left', xPos + '%')</code>, and not update <code>top</code>. This makes the style updates more efficient.</p>
<p>However, when using the <code>style</code> attribute, it is easy to accidentally opt-out of this optimization. If you construct the style attribute outside of the template, Svelte can't easily determine how to optimize it and won't try. Instead, it will set the <em>entire style attribute</em> when either variable is updated. So given this code...</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> xPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> yPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> boxStyle <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">left: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>xPos<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%; top: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>yPos<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><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name">style=</span><span class="token language-javascript"><span class="token punctuation">{</span>boxStyle<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>	(<span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span>, <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span>)<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>...Svelte won't set <code>left</code> and <code>top</code> individually, and instead sets the entire style attribute every time <code>xPos</code> or <code>yPos</code> changes:</p>
<pre class="language-js"><code class="language-js">div<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span>style<span class="token punctuation">,</span> boxStyle<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Per <a href="https://github.com/sveltejs/svelte/pull/810">the original PR</a> that added this optimization, this will almost always be slower than setting the individual properties. So, it's better to construct the style attribute directly in the template so that Svelte can optimize it.</p>
<p>However, with style directives, you don't need to think about any of this! Since each style directive corresponds to a single CSS property, it's easy for Svelte to make those same optimizations, even if the value comes from the <code>&lt;script&gt;</code> block.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> xPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> yPos <span class="token operator">=</span> <span class="token number">50</span><span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> left <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>xPos<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><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> top <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>yPos<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><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- This is optimized the same way as the original example --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style:</span>left</span> <span class="token attr-name"><span class="token namespace">style:</span>top</span><span class="token punctuation">></span></span><br>	(<span class="token language-javascript"><span class="token punctuation">{</span>xPos<span class="token punctuation">}</span></span>, <span class="token language-javascript"><span class="token punctuation">{</span>yPos<span class="token punctuation">}</span></span>)<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>By using style directives, you make sure that your dynamic styles are applied in an optimized way without needing to think about it.</p>
<h2>Limitations</h2>
<p>Style directives are great, but they have a few limitations.</p>
<p><strong>Exclusive to elements:</strong> Like most Svelte directives (with the exception of <code>on:</code>), this does not work on components. There is <a href="https://github.com/sveltejs/rfcs/pull/60">an open RFC</a> to allow forwarding directives to components, which would include style directives. However, this RFC has not been accepted at time of writing.</p>
<p><strong>Shorthand only works with dash-less properties:</strong> Since the property used in the style directive uses the same name as the equivalent CSS property, you can't use the shorthand with properties that contain a dash. This is because you can't use <code>-</code> in a JavaScript variable (e.g., you can't declare a variable with the name <code>border-color</code>).</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> This is valid <span class="token operator">--</span><span class="token operator">></span><br><span class="token operator">&lt;</span>div style<span class="token operator">:</span>border<span class="token operator">-</span>color<span class="token operator">=</span><span class="token punctuation">{</span>borderColor<span class="token operator">></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token operator">&lt;</span><span class="token operator">!</span><span class="token operator">--</span> This is not valid <span class="token operator">--</span><span class="token operator">></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">style:</span>border-color</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p><strong>No camel case:</strong> Some comments on the original RFC suggested allowing the camel-cased property as well (e.g. <code>style:borderColor</code>), but that suggestion was not accepted. The <a href="https://github.com/sveltejs/rfcs/pull/42#issuecomment-742110296">reasoning was</a> that it's more consistent with the rest of Svelte. For example, you need to do the same with class directives:</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">class:</span>is-active</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>isActive<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text"></code></pre>
<p><strong>No style object:</strong> <a href="https://reactjs.org/docs/dom-elements.html#style">Unlike React</a>, you can't pass an object of styles to the style attribute: it needs to be a string. If you do want to do this, it would be fairly simple to <a href="https://github.com/sveltejs/rfcs/pull/42#issuecomment-744560265">solve in userland</a>.</p>
<h2>Should you use the style directive for everything?</h2>
<p>You shouldn't use style directives for all of your component styles. I would avoid using it if your styles are purely static. For example, there is no need to use style directives on the following component:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style:</span>background-color</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>red<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">style:</span>height</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>100px<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span></code></pre>
<p>It would be better to put these styles in the style block of the component instead:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>	<span class="token selector">.box</span> <span class="token punctuation">{</span><br>		<span class="token property">background-color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br>		<span class="token property">height</span><span class="token punctuation">:</span> 100px<span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>This is better for performance, since everything in <code>&lt;style&gt;</code> is compiled to pure CSS without using any JavaScript to apply the styles. I would only use the style attribute and style directives for styles where the values are changing or are supplied from outside the component.</p>
<h2>Wrapping up</h2>
<p>This feature doesn't enable anything you couldn't do before, but it's a nice bit of syntactic sugar that helps ensure your inline styles are optimized. Go forth and style!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>The many meanings of $ in Svelte</title>
    <link href="https://geoffrich.net/posts/svelte-$-meanings/"/>
    <updated>2021-12-12T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-$-meanings/</id>
    
    <content type="html"><![CDATA[
      <p>If you're writing Svelte code, you'll notice that <code>$</code> can have multiple different meanings, depending on the context. For example, look at this code snippet — <code>$</code> is used in three different ways! If you're new to Svelte, or to JavaScript in general, it can be confusing to keep them all straight.</p>
<pre class="language-js"><code class="language-js"><span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Total: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$count<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></code></pre>
<p>In this post, I'll show all the different meanings that <code>$</code> can have in Svelte.</p>
<p>First, let's start with the primary uses of <code>$</code> in Svelte: reactive statements and reactive stores.</p>
<h2>Reactive statements</h2>
<p>In a Svelte component, prefixing a statement with <code>$:</code> marks the statement as <em>reactive</em> — it will run whenever the variables referenced in that statement change. Here's a classic example. Whenever <code>num</code> changes, <code>doubled</code> is automatically set to the correct value.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> num <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> doubled <span class="token operator">=</span> num <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Num: <span class="token language-javascript"><span class="token punctuation">{</span>num<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Doubled: <span class="token language-javascript"><span class="token punctuation">{</span>doubled<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> num<span class="token operator">++</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>	Increment<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>It's also possible to have an entire reactive block that runs when the variables referenced inside it change.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">let</span> num <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> doubled <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>  <span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>    doubled <span class="token operator">=</span> num <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>num<span class="token punctuation">,</span> doubled<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This is a core concept of Svelte. If you're not familiar with it, go review the section of the Svelte tutorial on <a href="https://svelte.dev/tutorial/reactive-declarations">reactivity</a>.</p>
<p>This is valid JavaScript, since it uses the obscure <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label">label</a> syntax. However, its reactive properties are unique to Svelte, and specifically to Svelte components. Using this syntax inside a regular <code>.js</code> file will not make a statement reactive.</p>
<p>Since this is a valid label, you can exit a reactive block early the same way you'd <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label#using_a_labeled_break_statement">break out of a regular label</a>. This example will keep track of the number of evens, but only until the counter reaches 10. At that point, <code>break $</code> will exit the block early.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> evens <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> <span class="token punctuation">{</span><br>		<span class="token keyword">if</span> <span class="token punctuation">(</span>counter <span class="token operator">></span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>			<span class="token keyword">break</span> $<span class="token punctuation">;</span><br>		<span class="token punctuation">}</span><br>		<span class="token keyword">if</span> <span class="token punctuation">(</span>counter <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>			evens<span class="token operator">++</span><span class="token punctuation">;</span><br>		<span class="token punctuation">}</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">on:</span>click=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>counter<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>	Increment<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>	Counter: <span class="token language-javascript"><span class="token punctuation">{</span>counter<span class="token punctuation">}</span></span>, evens before 10: <span class="token language-javascript"><span class="token punctuation">{</span>evens<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>You won't need to use this very often, but it's useful to know about.</p>
<h2>Accessing store values</h2>
<p>The other primary use of <code>$</code> you'll see in a Svelte component is when referencing the current value of a store. In Svelte, a <a href="https://svelte.dev/tutorial/writable-stores">store</a> is any object with a <code>subscribe</code> method that allows you to be notified when the value of the store changes. It's especially useful when you want a reactive value to be accessible from muliple components in your application, since the store can live outside of a Svelte component.</p>
<p>If you wanted to get the current value of a store in a Svelte component and have it automatically update when the store changes, you could do something like the following.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> count <span class="token keyword">from</span> <span class="token string">'./count'</span><span class="token punctuation">;</span><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>onDestroy<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> _count<span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> unsubscribe <span class="token operator">=</span> count<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">val</span> <span class="token operator">=></span> <span class="token punctuation">(</span>_count <span class="token operator">=</span> val<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">unsubscribe</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This code subscribes to the <code>count</code> store, updates the local <code>_count</code> variable when the store changes, and unsubscribe from the store when the component is destroyed. However, this is a lot of boilerplate.</p>
<p>Thankfully, Svelte has a special syntax to make this sort of thing easy. Inside a Svelte component, we can reference the current value of the <code>count</code> store with the variable <code>$count</code>. By using this syntax, Svelte will take care of subscribing and unsubscribing to the store for us.</p>
<p>As with reactive declarations, this syntax only works inside a Svelte component. In regular JS files, you'll need to subscribe to the store manually.</p>
<h3>Comparing reactive statements and reactive stores</h3>
<p>Those are the two primary ways <code>$</code> is used inside Svelte. If the dollar sign has a colon after it (<code>$:</code>), then it indicates a <a href="https://svelte.dev/docs#3_$_marks_a_statement_as_reactive">reactive statement</a>. If it is at the start of a variable name inside a Svelte component, then it's <a href="https://svelte.dev/docs#4_Prefix_stores_with_$_to_access_their_values">accessing a reactive store value</a>. In general, when you see <code>$</code> in a Svelte component, you should think <em>reactivity</em>.</p>
<p>Note that there are often times where you'll want to combine the two. Referencing a store value in the <code>&lt;script&gt;</code> block with <code>$</code> does <em>not</em> mean that value will automatically be updated when the store changes. In the following example, <code>doubledCount</code> will not be automatically updated unless you mark that assignment as reactive with <code>$:</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token comment">// count is a store</span><br>	<span class="token keyword">import</span> count <span class="token keyword">from</span> <span class="token string">'./count'</span><span class="token punctuation">;</span><br><br>	<span class="token comment">// doesn't keep value updated</span><br>	<span class="token keyword">let</span> doubledCount <span class="token operator">=</span> $count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br><br>	<span class="token comment">// keeps value updated</span><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> doubledCount <span class="token operator">=</span> $count <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>This could seem unintuitive — didn't I just say that a store is reactive? Yes, but it's only reactive in that <em>we can be notified any time the value changes</em>. If we want to derive a value from it, we still need to mark that statement as reactive as well.</p>
<p>This is a little difficult to wrap your head around, so see this alternate explanation in <a href="https://www.reddit.com/r/sveltejs/comments/r6j9r4/i_still_dont_get_this_and_need_an_eli5/hmy13ud/">r/sveltejs</a> if you're still having trouble.</p>
<p>However, those are not the only times you'll see <code>$</code> in a Svelte component. <code>$</code> is used in other ways, both in Svelte and in vanilla JavaScript in general. Let's go over a few more examples.</p>
<h2>Template literals</h2>
<p>This is not Svelte-specific, but is worth mentioning, since it's a common technique in modern JavaScript. When writing <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals">template literals</a> in JavaScript, you can use <code>${var}</code> to insert the value of a variable into the template string.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token string">'running'</span><span class="token punctuation">;</span><br><span class="token keyword">let</span> b <span class="token operator">=</span> <span class="token string">'walking'</span><span class="token punctuation">;</span><br><span class="token comment">// both output "running and walking"</span><br>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a <span class="token operator">+</span> <span class="token string">' and '</span> <span class="token operator">+</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// without template literals</span><br>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>a<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> and </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>b<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 comment">// with template literals</span></code></pre>
<p>Where this could get confusing is if you combine template literals with reactive statements and stores! Make sure you can pick out what each <code>$</code> means in the below example.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>writable<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> num <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">num is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>num<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> and the store is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>$count<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><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<h2>$$props, $$restProps, and $$slots</h2>
<p>These are globally-available variables inside a Svelte component. <code>$$props</code> contains all the props passed to the component, <code>$$restProps</code> contains all the props that were not explicitly exported by the component (which useful for wrapping native HTML elements like <code>&lt;input&gt;</code>), and <code>$$slots</code> contains the slots passed to the component. Here, $$ doesn't indicate that this is a store value; it's just a naming convention.</p>
<p>In fact, their naming mirrors how Svelte names things internally. For example, if you look at the code Svelte generates, you'll see reference to similarly-named variables like <code>$$self</code> and <code>$$invalidate</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">instance</span><span class="token punctuation">(</span><span class="token parameter">$$self<span class="token punctuation">,</span> $$props<span class="token punctuation">,</span> $$invalidate</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">let</span> name <span class="token operator">=</span> <span class="token string">'world'</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> <span class="token function-variable function">click_handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">$$invalidate</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>name <span class="token operator">+=</span> <span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> <span class="token punctuation">[</span>name<span class="token punctuation">,</span> click_handler<span class="token punctuation">]</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>The <a href="https://svelte.dev/docs#Client-side_component_API">client-side component API</a> also prefixes its methods with <code>$</code> to avoid colliding with methods defined on the component instance.</p>
<h2>Creating a derived store</h2>
<p>When creating a <a href="https://svelte.dev/docs#derived">derived store</a>, it is common to prefix the values of the store in the derived callback with <code>$</code>. You'll see this in the derived store examples in the Svelte docs.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>derived<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> doubled <span class="token operator">=</span> <span class="token function">derived</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> <span class="token parameter">$a</span> <span class="token operator">=></span> $a <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This is not required and does not indicate anything special — it works the same in and out of Svelte components. It just provides an easy way to distinguish the variable in the callback (which has the updated value of the store) and the reference to the store itself.</p>
<h2>Wrapping up</h2>
<p>While it might seem difficult to keep track of all these at first, given enough experience you'll get the hang of it. It's most important to understand the difference between the first three examples. You're less likely to encounter the others until you reach more advanced scenarios.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte&#39;s lifecycle methods can be used anywhere</title>
    <link href="https://geoffrich.net/posts/svelte-lifecycle-examples/"/>
    <updated>2021-11-30T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-lifecycle-examples/</id>
    
    <content type="html"><![CDATA[
      <p>I don't think it's widely known that you can call the Svelte lifecycle methods (onMount, onDestroy, beforeUpdate, afterUpdate) <em>outside</em> of a component. It is mentioned in the <a href="https://svelte.dev/docs#onMount">Svelte docs</a> and tutorial, though it's easy to gloss over.</p>
<blockquote>
<p>The <code>onMount</code> function schedules a callback to run as soon as the component has been mounted to the DOM. It must be called during the component's initialisation (but doesn't need to live <em>inside</em> the component; it can be called from an external module).</p>
</blockquote>
<p><code>onMount</code> and friends are <a href="https://github.com/sveltejs/svelte/blob/6ff1aed8d5051ef79063e8d6442d8b6d6def63a2/src/runtime/internal/lifecycle.ts#L14-L28">just functions</a> that schedule another function to run during a point in the current component's lifecycle. As long as you call these functions during a component's initialization, you can call them from anywhere. This means you can share lifecycle-dependent functionality between multiple components by putting it in a separate file, making it more reusable and reducing boilerplate.</p>
<p>Let's look at a few examples.</p>
<h2>Running a callback after a given interval</h2>
<p>You can write the following Svelte code to start a timer that tracks how long the page has been open. We wrap the <code>setInterval</code> call inside <code>onMount</code> so that it only runs in the browser, and not when the component is being server-rendered.</p>
<p>By returning a cleanup function from <code>onMount</code>, we tell Svelte to run that function when the component is being destroyed. This prevents a memory leak.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>onMount<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br><br>  <span class="token function">onMount</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> interval <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      count <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      <span class="token function">clearInterval</span><span class="token punctuation">(</span>interval<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>  This page has been open <span class="token language-javascript"><span class="token punctuation">{</span>count<span class="token punctuation">}</span></span> seconds.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>But what if you want to use this code in multiple components? You may have thought that because this code uses a component lifecycle method that it has to stay with the component. However, that's not the case. We can move this code to a separate module, as long as the function calling <code>onMount</code> is called when the component is initializing.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// interval.js</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>onMount<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">onInterval</span><span class="token punctuation">(</span><span class="token parameter">fn</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token function">onMount</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> interval <span class="token operator">=</span> <span class="token function">setInterval</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">clearInterval</span><span class="token punctuation">(</span>interval<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>onInterval<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./interval.js'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><br>  <span class="token function">onInterval</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>count <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span><br>  This page has been open <span class="token language-javascript"><span class="token punctuation">{</span>count<span class="token punctuation">}</span></span> seconds.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>Now we have the same behavior, but now it can be reused across multiple components! You can find a similar example using <code>onDestroy</code> instead in the <a href="https://svelte.dev/tutorial/ondestroy">Svelte tutorial</a>.</p>
<h2>A store that tells you if a component has been mounted</h2>
<p>We can also use <code>onMount</code> to make a store that tells you whether a component has finished mounting or not. This code is from the <a href="https://github.com/ghostdevv/svelte-mount">svelte-mount</a> package:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// mounted.js</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>onMount<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> mounted <span class="token operator">=</span> <span class="token punctuation">{</span><br>  <span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">fn</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token function">fn</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token function">onMount</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fn</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 punctuation">;</span><br>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>I found this a little hard to parse at first, but what we have here is a <strong>custom store</strong>. Per the <a href="https://svelte.dev/docs#Store_contract">Svelte docs</a>, a store is any object with a subscribe method that takes a subscription function. When a component subscribes to this store, the subscription function is first called with <code>false</code> . We then wrap a call to the subscription function in <code>onMount</code> so that it is set to true once the component is mounted.</p>
<p>Because this code is in the <code>subscribe</code> function, it will run for each component that subscribes to the store, meaning that <code>onMount</code> will refer to a different component's lifecycle each time it's called.</p>
<p>Here's an example of where this store would be useful. Normally, transitions don't play on initial render, so by adding the element after <code>onMount</code> has completed we allow the transition to play. By using the <code>mounted</code> store, we remove some boilerplate — we don't have to make a state variable to track if the component has mounted and update it in <code>onMount</code>. Nifty!</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>mounted<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./mounted'</span><span class="token punctuation">;</span><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>fade<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h1</span><span class="token punctuation">></span></span><br>  Hello world<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h1</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> $mounted<span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name"><span class="token namespace">in:</span>fade</span><span class="token punctuation">></span></span><br>  Component has been mounted.<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>You can also set the <code>intro</code> property when <a href="https://svelte.dev/docs#Creating_a_component">creating the component</a> to force transitions to play on initial render, though that won't work in a server-rendered context like SvelteKit.</p>
<h2>Track the number of times a component is rendered</h2>
<p>This example is a bit contrived, but still interesting. Someone asked a question on <a href="https://www.reddit.com/r/sveltejs/comments/q8hj8k/how_to_extract_crosscutting_concerns_in_svelte/">r/sveltejs</a> about how to track how many times a component has re-rendered in a way that can be shared across multiple components. They gave the following React hook as an example.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">useRenderCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> <span class="token punctuation">[</span>count<span class="token punctuation">,</span> setCount<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">setCount</span><span class="token punctuation">(</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> count<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token comment">// used in a component like so</span><br><span class="token keyword">function</span> <span class="token function">MyComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">useRenderCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token operator">&lt;</span>p<span class="token operator">></span><span class="token punctuation">{</span>count<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>p<span class="token operator">></span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Many folks suggested using the <a href="https://svelte.dev/docs#afterUpdate">afterUpdate</a> Svelte lifecycle method inside the component, but didn't realize that it could be moved outside the component as well. We can re-create this behavior completely independent from the component by combining <code>afterUpdate</code> with a writable Svelte store.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>writable<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>afterUpdate<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">trackUpdateCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token function">afterUpdate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    count<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> count<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>And it can be used like so, without needing to add any lifecycle boilerplate to the component itself:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- Input.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">export</span> <span class="token keyword">let</span> name <span class="token operator">=</span> <span class="token string">'test'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">import</span> trackUpdateCount <span class="token keyword">from</span> <span class="token string">'./trackUpdateCount'</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> count <span class="token operator">=</span> <span class="token function">trackUpdateCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Hello <span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span>! Updated <span class="token language-javascript"><span class="token punctuation">{</span>$count<span class="token punctuation">}</span></span> times<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name"><span class="token namespace">bind:</span>value="</span><span class="token language-javascript"><span class="token punctuation">{</span>name<span class="token punctuation">}</span></span><span class="token attr-name">"</span> <span class="token punctuation">/></span></span></code></pre>
<p>Here's a <a href="https://svelte.dev/repl/7a88abc02a444e44b80682ca8c32ef63?version=3.43.2">REPL</a> if you want to try it out.</p>
<p>I haven't quite wrapped my mind around it, but you can even use <code>afterUpdate</code> in Svelte to replicate React's useEffect hook. See this <a href="https://svelte.dev/repl/0c9cd8c29c5043eea89bd9c6eb4f279a?version=3.42.6">example from Rich Harris</a>, which I found in an interesting <a href="https://github.com/sveltejs/svelte/issues/6730">GitHub issue</a> discussing the edges of Svelte's reactivity.</p>
<h2>Cleaning up subscriptions</h2>
<p>Another common use of lifecycle methods is to clean up store subscriptions. When you use Svelte's special <code>$store</code> syntax inside a component, Svelte automatically subscribes to the store and unsubscribes when the component is destroyed. However, if you subscribe to a store in a regular JavaScript file, you need to unsubscribe manually. This is a great opportunity to use <code>onDestroy</code> — that way, a single file can handle the cleanup instead of requiring the importing components to do it.</p>
<p>At a high level, it could look something like this. Note that this is in an external file, <em>not</em> a Svelte component.</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// store.js</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>writable<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>onDestroy<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">createStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> items <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> unsubscribeItems <span class="token operator">=</span> items<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">$items</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token comment">// do something when items changes</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token comment">// we clean up the subscription ourselves,</span><br>  <span class="token comment">// instead of making the component do it</span><br>  <span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">unsubscribeItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> items<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>We can then call this function to initialize the store in a component, and the subscription from this file will be automatically cleaned up when the component is destroyed.</p>
<p>For a more concrete example, take a look at this function. We return two stores, <code>items</code> and <code>selected</code>. The <code>selected</code> store is used to track which items are selected, and is generally controlled by the consuming component. However, when items changes, we want to do one of two things:</p>
<ol>
<li>If all items were selected, all items should still be selected (regardless of any overlap)</li>
<li>If a subset of items were selected, we should keep any common items selected. So if <code>selected</code> was <code>[2,3]</code> and the new items are <code>[2,4,6]</code>, then we should update selected to be <code>[2]</code> .</li>
</ol>
<p>Here's what the function looks like, and a <a href="https://svelte.dev/repl/ca0cc9c7ae284101a2676d830bc511ad?version=3.44.2">REPL</a> to demo how it's used.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>writable<span class="token punctuation">,</span> get<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>onDestroy<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">createSelectedStore</span><span class="token punctuation">(</span><span class="token parameter">initialItems</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> items <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span>initialItems<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">const</span> selected <span class="token operator">=</span> <span class="token function">writable</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> oldItems <span class="token operator">=</span> initialItems<span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> unsubscribeItems <span class="token operator">=</span> items<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">$items</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> _selected <span class="token operator">=</span> <span class="token function">get</span><span class="token punctuation">(</span>selected<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>oldItems<span class="token punctuation">.</span>length <span class="token operator">===</span> _selected<span class="token punctuation">.</span>size<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      <span class="token comment">// if all items were selected, select all of the new items</span><br>      selected<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token operator">...</span>$items<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>      <span class="token comment">// otherwise, only select items that are shared between the old and new set</span><br>      <span class="token keyword">const</span> commonItems <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>$items<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> _selected<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>      selected<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>commonItems<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>    oldItems <span class="token operator">=</span> $items<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">unsubscribeItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">{</span><br>    items<span class="token punctuation">,</span><br>    selected<br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Because we subscribe to the items store so that we can update the selected store, we need to unsubscribe from it in <code>onDestroy</code>.</p>
<p>In practice, I used a store like this in <a href="https://marvel.geoffrich.net/year/1984">my site to filter Marvel comics</a> released in a given year. For each year, users can filter the list of comics for different creators (e.g. only view comics by Chris Claremont). When changing from one year to the next, I want to preserve the filter state as outlined above — if the creators for the next year contain creators that were selected from the previous year, those creators should stay selected.</p>
<p>I simplified my implementation of this for the above example, but you can find the original code <a href="https://github.com/geoffrich/marvel-by-year/blob/9bf6c252a68dec46d49f37fff215806823c5617e/src/lib/stores/selected.ts">on GitHub.</a></p>
<h2>Wrapping up</h2>
<p>You won't run into this use case commonly, and not every example I showed needs to be done this way. For some of these examples, you can get a similar outcome using a store. However, it's good to keep this technique in mind for when it becomes necessary.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Introduction to Svelte Actions</title>
    <link href="https://blog.logrocket.com/svelte-actions-introduction/"/>
    <updated>2021-11-26T00:00:00Z</updated>
    <id>https://blog.logrocket.com/svelte-actions-introduction/</id>
    
    <summary>Posted on LogRocket: Svelte actions are a very powerful feature of Svelte. I break down two situations where an action makes your code simpler and more reusable.</summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Svelte Summit 2021: Svelte Transitions and Accessibility</title>
    <link href="https://geoffrich.net/posts/svelte-summit-2021/"/>
    <updated>2021-11-20T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-summit-2021/</id>
    
    <content type="html"><![CDATA[
      <script src="/node_modules/lite-youtube-embed/src/lite-yt-embed.js"></script>
<lite-youtube videoid="iceNAO8c4J4" style="background-image: url('https://i.ytimg.com/vi/iceNAO8c4J4/hqdefault.jpg');" >
  <button type="button" class="lty-playbtn">
    <span class="lyt-visually-hidden">Play Video: Geoff Rich - Svelte Transitions and Accessibility</span>
  </button>
</lite-youtube>
<p>I spoke at the Fall 2021 <a href="https://sveltesummit.com/">Svelte Summit</a> on Svelte Transitions and Accessibility. Svelte includes built-in animations that makes it easy to slide, scale, and fly elements in and out of the DOM. However, you need to be careful to not trigger motion sickness in your users. In my talk, I go over which Svelte transitions could cause accessibility issues and how to respect user motion preferences when using them.</p>
<p>We iteratively build a solution to change which transition we use when the user requests reduced motion -- first only using CSS, then detecting the user's motion preference in a Svelte component, to finally encapsulating the logic in a reusable Svelte store.</p>
<p>Following is a transcript of the talk. You can find my <a href="https://docs.google.com/presentation/d/1djta1tNkYdN7B287S3U9Stfo0L8q0lfgsVQGUKBu2N4/edit?usp=sharing">slide deck</a> on Google Slides and the <a href="https://github.com/geoffrich/svelte-summit-accessible-transitions">example code</a> on my GitHub. The entire event is available to stream on <a href="https://youtube.com/playlist?list=PL8bMgX1kyZTg2bI9IOMgfBc8lrU3v2itt">YouTube</a>.</p>
<p>Huge shout-out to the organizers for putting on such a great event and for featuring my talk! Special thanks to Kevin for being very quick to respond to my questions via email, and to Shawn (a.k.a. swyx) for hosting a speaker prep session with some very helpful tips.</p>
<hr>
<p>Hi, I'm Geoff. I'm a software engineer at Alaska Airlines and today I want to talk about Svelte transitions.</p>
<p>Svelte transitions are amazing! I love that with only a few lines of code and no additional dependencies, you can add some animation that can really make your app stand out. However, you need to be careful. Some people are sensitive to motion. If you use these transitions irresponsibly, they can trigger headaches, dizziness, and even nausea—and this is not how people should feel when they visit your website!</p>
<p>Remember — to quote the <a href="https://svelte.dev/tutorial/custom-css-transitions">official Svelte transition tutorial</a>, &quot;with great power comes great responsibility.&quot;</p>
<p>In this talk, I'll talk about the types of animation that can cause accessibility issues, how to detect if the user has requested reduced motion, and how to apply these concepts to Svelte transitions</p>
<h2>Motion and accessibility</h2>
<p>First, let's talk about motion and accessibility.</p>
<p>So, who does this affect? Well, the largest group affected by animation on the web is people with vestibular disorders. The vestibular system controls your body's sense of balance. If it doesn't function properly, you could experience dizziness, loss of balance, and vertigo, among other symptoms. And animation can often be a trigger for these effects.</p>
<p>According to the National Institute on Deafness and Other Communication Disorders, 4% of American adults report a chronic problem with balance. That's millions of people, many of whom could be using your website or app.</p>
<p>So what kinds of animation can cause issues? Well, there are a few factors.</p>
<p>In general, you want to avoid large amounts of movement. So a button moving a few pixels when you hover it is okay, but something flying across the entire screen could cause an issue. You also want to be careful with multiple elements moving at the same time, especially if they're moving in different directions or at different speeds, such as parallax type movement. And tying any movement to the user's scroll position could be problematic, especially if the element is moving at a different speed or direction from how the user is scrolling.</p>
<p>I recommend the A List Apart article <a href="https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/">&quot;Designing Safer Web Animation for Motion Sensitivity&quot;</a> from Val Head if you want more detail on this.</p>
<p>So with that in mind, what Svelte transitions deserve extra caution? Well, of the 7 built-in Svelte transitions, 5 of them involve motion. Those would be fly, slide, scale, draw, and crossfade. Transitions like fade and blur that do <em>not</em> involve motion are unlikely to cause issues. This doesn't mean that all uses of these transitions are problematic. What you should look out for is the amount and direction of the motion.</p>
<p>So does that mean if you care about accessibility, you're not allowed to use these transitions? Of course not! But you should provide an alternate experience for users who are sensitive to motion. And that's where prefers-reduced-motion comes in.</p>
<h2>Prefers reduced motion</h2>
<p>So, Operating systems have started to add ways for users to request reduced motion. [slides show image of iOS and Mac accessibility settings] Here is where you can find it on iOS and Mac, where it's called &quot;reduce motion&quot;. [slides show Windows settings and Chrome DevTools] And here's where it is on Windows, under &quot;show animations in Windows&quot;. Chrome also lets you set it directly in DevTools, so you can emulate that experience without having to adjust your OS settings.</p>
<p>Here's a video of how iOS behaves when you have this setting enabled. Motion warning for the next few seconds. Normally, opening an app triggers a zooming animation where the app expands into view. However, if you turn on this setting [reduced motion], apps fade in and out instead.</p>
<p>Originally this setting was just for users to control their operating system, but it's also available as a media query in web browsers, and it has great browser support. So, we can detect if a user has requested reduced motion, and adjust our site accordingly.</p>
<p>So let's recap. We know that large amounts of motion can trigger dizziness and nausea in some of our users. We have a media query that can detect if they've requested reduced motion. But how do we apply that to transitions in our Svelte application? Well, there's a few different options. Enough slides. Let’s jump to some code.</p>
<h2>Live demo</h2>
<p>First, let me show you where we're starting. We have a checkbox here that will hide and show this box. The box has a fly transition applied, so that it flies in and out. I set the y parameter very low to minimize any triggering motion.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> fly <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> show <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>checked=</span><span class="token language-javascript"><span class="token punctuation">{</span>show<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Show box<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> show<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">transition:</span>fly=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">30</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>emoji<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>📦<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>Here's what the transition looks like with reduced motion disabled. [box flies in and out]</p>
<p>So, the first way to respect reduced motion is to disable all animation entirely. Svelte's built-in transitions are applied using native CSS animations, so this snippet will prevent those animations from playing if reduced motion is turned on. Because Svelte's transitions use inline styles, we need to use <code>!important</code> here so that this override applies.</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br>  <span class="token selector">*</span> <span class="token punctuation">{</span><br>    <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">animation-delay</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>So, this seems to work. If I enable prefers-reduced motion in Chrome, then the fly animation doesn't play. Is that all there is to it? Well, this approach has some drawbacks.</p>
<p>It's definitely better than not respecting motion preference at all. However, we use animation for a reason. Animation is often essential to make your interface understandable. So we don't want to turn it off entirely.</p>
<p>Remember: it's prefers <em>reduced</em> motion, not prefers <em>no</em> motion.</p>
<p>Instead, we could behave more like iOS does—if reduced motion is requested, change the animation we use to have less motion. On iOS, they change the zoom to a fade instead when an app opens. We could do the same — fade the element in instead of flying it in.</p>
<p>Of course, this approach requires more vigilance on the part of the developer. You need to be intentional when implementing animation and choose what the fallback should be for users with motion sensitivities. But, this approach will result in a richer experience for those users as well.</p>
<p>Even if you do choose to globally disable animation like this, remember that some Svelte animations are applied purely through JavaScript, not CSS. Custom JavaScript transitions and animation using the spring or tweened stores won't be affected by a CSS-only solution. So, it's worthwhile to consider other methods as well.</p>
<p>Now, let's look at how we can detect reduced motion in JavaScript, and change the transition we use accordingly.</p>
<p>This is the same component as before, except now the transition we use is stored in a variable. <code>transitionToUse</code> will reactively update based on the value of <code>reducedMotion</code>. If reduced motion is true, we'll use the fade transition. Otherwise, we'll use the fly transition. But for that to work, we need to set <code>reducedMotion</code> properly.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> fly <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> show <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> reducedMotion <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> transitionToUse <span class="token operator">=</span> reducedMotion <span class="token operator">?</span> fade <span class="token operator">:</span> fly<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>checked=</span><span class="token language-javascript"><span class="token punctuation">{</span>show<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Show box<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> show<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">transition:</span>transitionToUse=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">30</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>emoji<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🐱<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>First, we'll call <code>window.matchMedia</code> to get a media query list that will tell us if the reduced motion media query applies. We can also add an event listener to this list, so that if the user changes their motion preference, we can immediately react to it instead of only checking on initial load. And just like any time when we add an event listener manually, we need to remove it when the component is destroyed. Finally, if you're doing this in a server-side rendering context like SvelteKit, you need to make sure we're in the browser so that we can use a function from the window.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> fly<span class="token punctuation">,</span> fade <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> onDestroy <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> browser <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/env'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> show <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>	<span class="token keyword">let</span> reducedMotion <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> transitionToUse <span class="token operator">=</span> reducedMotion <span class="token operator">?</span> fade <span class="token operator">:</span> fly<span class="token punctuation">;</span><br><br>	<span class="token keyword">const</span> reducedMotionQuery <span class="token operator">=</span> <span class="token string">'(prefers-reduced-motion: reduce)'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">if</span> <span class="token punctuation">(</span>browser<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>		<span class="token keyword">let</span> mediaQuery <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span>reducedMotionQuery<span class="token punctuation">)</span><span class="token punctuation">;</span><br>		reducedMotion <span class="token operator">=</span> mediaQuery<span class="token punctuation">.</span>matches<span class="token punctuation">;</span><br><br>		<span class="token keyword">const</span> <span class="token function-variable function">setReducedMotion</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>			reducedMotion <span class="token operator">=</span> event<span class="token punctuation">.</span>matches<span class="token punctuation">;</span><br>		<span class="token punctuation">}</span><span class="token punctuation">;</span><br><br>		mediaQuery<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> setReducedMotion<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>		<span class="token function">onDestroy</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>			mediaQuery<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> setReducedMotion<span class="token punctuation">)</span><span class="token punctuation">;</span><br>		<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>	<span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>checked=</span><span class="token language-javascript"><span class="token punctuation">{</span>show<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Show box<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> show<span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">transition:</span>transitionToUse=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">30</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>emoji<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🐱<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>And that's all there is to it! When we set prefers reduced motion, the box now fades in. After we unset it, the box flies in.</p>
<p>So this works great — now let's make it more reusable. Stores are a great way to extract reactive logic in Svelte. I've updated this example to use a store instead of a local variable, but we still need to implement it.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> fly<span class="token punctuation">,</span> fade <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> reducedMotion <span class="token keyword">from</span> <span class="token string">'$lib/reducedMotionStore'</span><span class="token punctuation">;</span><br><br>	<span class="token keyword">let</span> show <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>	<span class="token literal-property property">$</span><span class="token operator">:</span> transitionToUse <span class="token operator">=</span> $reducedMotion <span class="token operator">?</span> fade <span class="token operator">:</span> fly<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>input<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkbox<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">bind:</span>checked</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{show}</span> <span class="token punctuation">/></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>show<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Show box<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>{#if show}<br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>box<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">transition:</span>transitionToUse</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>{{</span> <span class="token attr-name"><span class="token namespace">y:</span></span> <span class="token attr-name">30</span> <span class="token attr-name">}}</span><span class="token punctuation">></span></span><br>		<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>emoji<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>🐶<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>{/if}</code></pre>
<p>First, we'll get the initial value of the store using the query from before, defaulting to false if we're not in the browser. Then, we'll pass a function as the second argument to the store. This function will be called when the store gets its first subscriber, so it's a great place to set up event listeners. We'll add the listener from before to react to preference changes. We can return a cleanup function to remove the event listener when there are no more subscribers. And just like before, we only want to run this code in the browser.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>readable<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>browser<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'$app/env'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> reducedMotionQuery <span class="token operator">=</span> <span class="token string">'(prefers-reduced-motion: reduce)'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">getInitialMotionPreference</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>browser<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span>reducedMotionQuery<span class="token punctuation">)</span><span class="token punctuation">.</span>matches<span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">readable</span><span class="token punctuation">(</span><span class="token function">getInitialMotionPreference</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token parameter"><span class="token keyword">set</span></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>browser<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> <span class="token function-variable function">setReducedMotion</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      <span class="token function">set</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>matches<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">;</span><br>    <span class="token keyword">const</span> mediaQueryList <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span>reducedMotionQuery<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    mediaQueryList<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> setReducedMotion<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>      mediaQueryList<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> setReducedMotion<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And there you have it — a reactive, reusable store to detect the user's motion preference.</p>
<h2>Could Svelte make this easier?</h2>
<p>So, writing all this code begs the question: could Svelte make any of this easier? And my answer is: maybe! At minimum, I think it would be good to document the need to respect motion preferences when using Svelte transitions.</p>
<p>However, I think it's tricky to figure out just how much of what I just showed could or should be integrated into Svelte itself. For instance, I wouldn't want to add that animation-disabling CSS snippet into the template and call it a day. As I talked about earlier, there's a lot more nuance involved there. At its core, this is a design question, and there isn't going to be an answer that's the same for every app. But maybe there's a way to make it easier to adjust animation based on motion preference, by adding similar code to what I showed you.</p>
<p>There is an open <a href="https://github.com/sveltejs/svelte/issues/5346">GitHub issue</a> around this already. If you have thoughts or suggestions as to how to make this sort of thing easier, go comment over there. But at the end of the day, it’s our responsibility to make sure the code we write is accessible. Anything Svelte does won't change that.</p>
<p>Thanks for watching. If you want to learn more, here are some resources I found helpful while preparing for this talk, by designers and developers much more knowledgeable about this than I am. At the bottom are two articles I wrote on my personal blog about this topic earlier this year. They formed the foundation for this talk.</p>
<ul>
<li>Smashing Magazine, <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">“Respecting Users’ Motion Preferences”</a> and <a href="https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/">“Designing With Reduced Motion for Motion Sensitivities”</a></li>
<li>CSS-Tricks, <a href="https://css-tricks.com/revisiting-prefers-reduced-motion-the-reduced-motion-media-query/">“Revisiting prefers-reduced-motion, the reduced motion media query”</a></li>
<li>web.dev, <a href="https://web.dev/prefers-reduced-motion/">“prefers-reduced-motion: Sometimes less movement is more”</a></li>
<li>geoffrich.net, <a href="/posts/svelte-prefers-reduced-motion-store/">“A Svelte store for prefers-reduced-motion”</a>
and <a href="/posts/accessible-svelte-transitions/">“Accessible Svelte Transitions”</a></li>
</ul>
<p>You can find me on Twitter <a href="https://twitter.com/geoffrich_">@geoffrich_</a>, or at my personal site, <a href="/">geoffrich.net</a>, where I regularly write about Svelte. If you have any questions, feel free to reach out on Twitter. Thanks again!</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Building SvelteKit Applications with Serverless Redis</title>
    <link href="https://blog.upstash.com/svelte-with-serverless-redis"/>
    <updated>2021-11-08T00:00:00Z</updated>
    <id>https://blog.upstash.com/svelte-with-serverless-redis</id>
    
    <summary>Posted on Upstash: How to integrate Redis into a SvelteKit app.</summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>What Svelte&#39;s accessibility warnings won&#39;t tell you</title>
    <link href="https://geoffrich.net/posts/svelte-a11y-limits/"/>
    <updated>2021-10-18T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-a11y-limits/</id>
    
    <content type="html"><![CDATA[
      <p>Svelte's accessibility (often shortened to &quot;a11y&quot;) warnings are one of the framework's standout features. Per <a href="https://twitter.com/rich_harris/status/1008856270084898816">Rich Harris</a>, Svelte is an &quot;a11y-first framework&quot; that &quot;will <em>let</em> you write non-accessible markup, but it won't respect you for it.&quot; Accessibility warnings in the compiler have been a part of the framework <a href="https://github.com/sveltejs/svelte/issues/374">since version 1.38</a>, and are highlighted in the <a href="https://svelte.dev/tutorial/dynamic-attributes">first section</a> of the tutorial.</p>
<p>When I was first learning about Svelte, I was intrigued by this feature. However, I couldn't find much discussion on what these warnings include. Most mentions of this feature only give image alt text as example—certainly important, but only a small part of making an accessible web page. Also, until recently, there wasn't much mention of accessibility in the Svelte docs, though now there's a list of the <a href="https://svelte.dev/docs#Accessibility_warnings">compiler a11y warnings</a>.</p>
<p>In this post I will answer a few questions:</p>
<ul>
<li>Do frameworks make for less accessible sites?</li>
<li>Just how effective are Svelte's a11y warnings?</li>
<li>What won't they warn you about?</li>
<li>How could Svelte improve?</li>
</ul>
<h2>The curse of React</h2>
<p>Yes, in an article about Svelte I'm starting off by talking about React. Bear with me.</p>
<blockquote>
<p>[React] has greatly simplified the building of complex interactions so everybody rolls their own--but they don't realize they've messed up the accessibility of their apps.</p>
<p>— <a href="https://twitter.com/ryanflorence/status/1095853086478761984?s=20">Ryan Florence</a></p>
</blockquote>
<p>The <a href="https://www.youtube.com/watch?v=orq9XnHGTgQ">Curse of React</a> is that the average React app is <em>less</em> accessible than the average jQuery app. This is because React simplified building UI to the point that developers write their own complex UI components instead of using a standard library like jQuery UI. However, the average developer doesn't know everything that's needed to make something like an autocomplete accessible, resulting in less accessible components.</p>
<p>This curse isn't unique to React—at this point, the Curse of React is really the curse of UI frameworks in general, including Svelte. Since Svelte is still a developing ecosystem, it suffers even more from the Curse of React. There aren't a lot of options for Svelte component libraries, let alone accessible ones. And because React is still the biggest framework, the best thinkers in accessibility are focusing on that ecosystem (e.g. <a href="https://react-spectrum.adobe.com/react-aria/">React ARIA</a>, <a href="https://www.downshift-js.com/">Downshift</a>, and others).</p>
<blockquote>
<p>I hesitated to mention this last point because it technically has to do with adoption, but I cannot separate it from React's merits: it seems to have the best thinkers on accessibility and interaction design right now. No other ecosystem has projects like Adobe and Devon Govett's React Aria that has extensively thought through and tested for WAI-ARIA so you don't have to. Ditto Segun Adebayo's Chakra UI.</p>
<p>—Swyx, <a href="https://dev.to/swyx/svelte-for-sites-react-for-apps-2o8h">Svelte for Sites, React for Apps</a></p>
</blockquote>
<p>So, just like React, it's easy to build an inaccessible component with Svelte. But because Svelte is relatively small, there aren't any battle-tested, widely used component libraries that are more likely to be accessible and there's less for Svelte devs to reuse.</p>
<p>But Svelte's compiler has accessibility warnings, right? So won't that make my Svelte app accessible?</p>
<p>Well, it's complicated. But in summary—no.</p>
<h2>What accessibility issues does the Svelte compiler catch?</h2>
<p>First, I want to review which accessibility issues the compiler will warn you about. You can find all the warnings listed in the <a href="https://svelte.dev/docs#Accessibility_warnings">Svelte docs</a>. In addition, the code behind these warnings is very readable. If you're interested, look at the <a href="https://github.com/sveltejs/svelte/blob/master/src/compiler/compile/nodes/Element.ts">Element.ts</a> file in the Svelte compiler and search for &quot;a11y&quot;.</p>
<p>Reviewing each warning individually could get pretty dry, so I'll provide a high-level overview of the types of issues that will trigger a compiler warning.</p>
<p>Most of Svelte's accessibility warnings focus around attributes on single HTML elements:</p>
<ul>
<li>required attributes that are missing (e.g. no <code>alt</code> attribute)</li>
<li>misplaced attributes that shouldn't be there (e.g. <code>aria-hidden</code> on a heading)</li>
<li>invalid attributes (e.g. writing <code>role=&quot;potato&quot;</code>)</li>
</ul>
<p>There are also some checks around the structure of the markup in a single component, e.g.:</p>
<ul>
<li><code>&lt;figcaption&gt;</code> should be a child of <code>&lt;figure&gt;</code></li>
<li>label should have a <code>for</code> attribute or a child <code>&lt;input&gt;</code></li>
<li>anchors and headings should have child text content</li>
</ul>
<p>The rest of the warnings are a grab bag of accessibility best practices—markup that is technically valid, but is not recommended due to its accessibility impact, e.g.:</p>
<ul>
<li>Don't use <code>&lt;marquee /&gt;</code></li>
<li>Don't use <code>autofocus</code></li>
<li>Don't use positive <code>tabindex</code> values</li>
</ul>
<p>Most of Svelte's checks are copied from <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#supported-rules">eslint-plugin-jsx-a11y</a>. There is also <a href="https://github.com/sveltejs/svelte/issues/820">an open GitHub issue</a> detailing additional checks the Svelte team would like to add.</p>
<h2>What issues will the compiler overlook?</h2>
<p>However, even if all the suggested rules in the above GitHub issue were added, there are still large categories of issues that the Svelte compiler will overlook. If you take one thing away from this post, let it be this:</p>
<p><strong>Just because you don't see any Svelte compiler warnings doesn't mean you made an accessible website.</strong></p>
<p>I will focus on issues that aren't detected because they're difficult or impossible to detect with a compiler, not just because no one has implemented them yet.</p>
<h3>Dynamic values</h3>
<p>If the value of an attribute is dynamic, the compiler can't be sure what will be placed in that attribute at runtime and will not validate that value.</p>
<p>For example, the compiler warns you if you write <code>&lt;a href=&quot;#&quot;&gt;</code>. But if you make a variable that stores <code>&quot;#&quot;</code> and set href to that variable, the compiler won't warn you. This also applies if the value of that attribute is a component prop.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">let</span> href <span class="token operator">=</span> <span class="token string">"#"</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href=</span><span class="token language-javascript"><span class="token punctuation">{</span>href<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>I'm under the radar<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre>
<p>This isn't a warning because it's hard for the compiler to determine all the possible values for that variable at compilation time, especially if that variable is populated by an external API response. It can't know if the value of that attribute is an accessibility issue or not.</p>
<p>This limitation is true for any attribute that the compiler would normally warn you about.</p>
<h3>Anything that requires a larger view of the app</h3>
<p>Not everything can be detected at the component level. Some issues depend on how the component is used in an application, or on an element present in another component. Many of these checks are easier to do at runtime with a tool like <a href="https://www.deque.com/axe/devtools/">Axe</a>, which has a full view of the rendered application. Svelte's compiler only looks at one component at a time, and has a limited view of the app as a whole.</p>
<p>For example, you shouldn't <a href="https://www.w3.org/WAI/tutorials/page-structure/headings/">skip heading levels</a> and go from an <code>&lt;h2&gt;</code> to an <code>&lt;h4&gt;</code>. However, if each heading is in a different component, Svelte won't know that you're skipping a heading level. It's not possible to determine that using static analysis.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- Heading2.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- Heading4.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h4</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h4</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- App.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> Heading2 <span class="token keyword">from</span> <span class="token string">'./Heading2.svelte'</span><span class="token punctuation">;</span><br>	<span class="token keyword">import</span> Heading4 <span class="token keyword">from</span> <span class="token string">'./Heading4.svelte'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Heading2</span><span class="token punctuation">></span></span><br>	I'm an h2<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Heading2</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Heading4</span><span class="token punctuation">></span></span><br>	I'm an h4<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>Heading4</span><span class="token punctuation">></span></span></code></pre>
<p>Similarly, <a href="https://dequeuniversity.com/rules/axe/3.5/duplicate-id">duplicate IDs</a> can be an accessibility issue. If two inputs have the same ID, the browser won't know which label goes with which input. However, if you use the same ID in two different Svelte components, the compiler won't be able to determine if that's an issue. Even if it was looking for duplicate IDs, it doesn't know if those two components are ever rendered at the same time.</p>
<p>Even warnings the Svelte compiler does have, like <a href="https://svelte.dev/docs#a11y-label-has-associated-control">labels must be linked to an input</a>, aren't perfect and have blind spots. With the way this warning is currently implemented, Svelte only requires the label to have a <code>for</code> attribute or to wrap an input. It doesn't require an input with the matching <code>id</code> to exist or for the input to be associated with a label.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- This passes, even if there is no input with id="test" --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>label</span> <span class="token attr-name">for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>test<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>label</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- This passes, even though there is no associated label --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text<span class="token punctuation">"</span></span><span class="token punctuation">></span></span></code></pre>
<p>This is because it's very hard for the compiler to be certain that there is an accessibility issue here. In the first case, there could be an input with <code>id=&quot;test&quot;</code> <em>somewhere</em>, be that another component or even outside the Svelte app entirely. The second case with the standalone input could be easier to detect, but any implementation would likely introduce false positives, where the compiler reports an accessibility issue that is not there.</p>
<p>This is one of the central conundrums of the Svelte compiler's accessibility checks: finding the balance between false positives and false negatives. The compiler optimizes for <em>false negatives</em>, or not reporting accessibility issues that are present, so that the number of <em>false positives</em> is minimized. If there are too many false positives, people stop trusting the accessibility warnings. However, this means there are many potential accessibility issues that the compiler will not detect.</p>
<h3>Styling issues</h3>
<p>If the accessibility issue is in CSS, Svelte won't detect it. Two examples of these issues are making sure your text has appropriate <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast">color contrast</a> and that <a href="https://www.deque.com/blog/accessible-focus-indicators/">keyboard focus</a> is visible when navigating the page.</p>
<p>These issues are unlikely to become a compiler warning. As above, this is way easier to check in the browser.</p>
<h3>Anything that's subjective</h3>
<p>If it can't be a straightforward yes/no answer, the compiler is not going to warn you about it. Svelte's current accessibility checks are just lint rules: they're going to help you get the little things right, but they're not going to guarantee that you've written good code. For example:</p>
<ul>
<li>Is animation on the page going to <a href="https://web.dev/prefers-reduced-motion/">trigger motion sickness</a>?</li>
<li>Is there a better <a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">semantic HTML</a> element you could use?</li>
<li>Is your <a href="https://webaim.org/techniques/alttext/">alt text</a> meaningful? Is that image really decorative?</li>
<li>Is the page usable when used with <a href="https://webaim.org/articles/visual/lowvision#magnifiers">screen magnification</a> software?</li>
<li>Is your custom dropdown/modal/autocomplete usable by a screen reader? By voice controls? By someone only using the keyboard?</li>
</ul>
<p>Accessibility can't be reduced to a series of compiler warnings. So much about accessibility is a spectrum, not a binary yes/no. These just aren't things that a automated checker can be certain about.</p>
<h3>Summing up</h3>
<p>Those are some of the issues the compiler is unlikely to warn you about anytime soon. And I don't think we should expect it to. There are limitations to being a compiler, and many of these issues are much easier to check in the browser using another automated tool or though manual testing.</p>
<p>And honestly, there are limits to automated accessibility checks. In general, passing some sort of automated accessibility check is <strong>not</strong> a guarantee that your page is accessible, the Svelte compiler included. For more on this, I recommend <a href="https://accessibility.blog.gov.uk/2017/02/24/what-we-found-when-we-tested-tools-on-the-worlds-least-accessible-webpage/">&quot;What we found when we tested tools on the world’s least-accessible webpage&quot;</a> and <a href="https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/">&quot;Building the most inaccessible site possible with a perfect Lighthouse score.&quot;</a> As developers, we can't pass the responsibility of making our sites accessible to an automated tool.</p>
<p>The question is: do Svelte developers understand these limitations exist?</p>
<h2>False confidence</h2>
<p>I put a <a href="https://twitter.com/geoffrich_/status/1381999698643275777">poll on Twitter</a> a few months ago:</p>
<blockquote>
<p>I expect Svelte's accessibility warnings to catch ____ a11y issues.</p>
<p>Where a11y issue is anything that impacts the accessibility of a page (misused attributes, keyboard focus, color contrast, etc)</p>
<ul>
<li>Some (&lt;20% of issues)</li>
<li>Many (20-50%)</li>
<li>Most (&gt;50%)</li>
<li>All a11y issues</li>
</ul>
<p>— <a href="https://twitter.com/geoffrich_/status/1381999698643275777">@geoffrich_</a> on April 13, 2021</p>
</blockquote>
<p>You can't really quantify a11y compliance with percentages—calling your app &quot;80% accessible&quot; is nonsensical. But I wanted to understand people's confidence. If you're building your app in Svelte and you know Svelte has accessibility warnings, what do you think seeing no accessibility warnings means? Does it mean your app is fully accessible? And the results were surprising:</p>
<ol>
<li>Most a11y issues (28.5% of respondents)</li>
<li>Some a11y issues (26.3%)</li>
<li>All a11y issues (24%)</li>
<li>Many a11y issues (21.2%)</li>
</ol>
<p>Out of 300 respondents, over half (52%) thought Svelte would catch most or all a11y issues. Almost a quarter thought Svelte would catch all of them. And I don't think that's a correct perception. With my examples above, it's clear that there's so much more to accessibility that Svelte won't warn you about.</p>
<p>Even beyond Svelte, automated accessibility checkers don't catch every issue. According to WebAIM, only <a href="https://webaim.org/projects/million/#method">25%-35% of accessibility errors</a> are detectable using any automated tooling, and the Svelte compiler is a subset of that. You're mistaken if you think using Svelte will mean you're warned about any accessibility issue.</p>
<p>I want to emphasize that <em>Svelte hasn't promised that</em>—there's no false advertising happening here. But there also isn't a lot of documentation on Svelte's accessibility warnings. Accessibility is mentioned in the <a href="https://svelte.dev/tutorial/dynamic-attributes">tutorial</a> once. If you're unfamiliar with accessibility, I see how you could assume that it's something the compiler takes care of, just like the compiler takes care of optimizing your code.</p>
<h2>How could Svelte be better?</h2>
<p>Developers will always have to do something to make sure what they build is accessible. However, I have a few suggestions for Svelte to improve its accessibility tooling and documentation.</p>
<h3>Integrate with existing tooling</h3>
<p>A lot of the existing a11y warnings have been slow to implement because Svelte has to re-implement work already done in the <a href="https://github.com/jsx-eslint/eslint-plugin-jsx-a11y">JSX eslint</a> plugin or in <a href="https://github.com/dequelabs/axe-core">axe-core</a>. Is there a way to use existing packages in the Svelte compiler to detect a11y issues? Building a compiler is hard enough, let alone keeping up to date with accessibility guidance.</p>
<p>Also, with <a href="https://kit.svelte.dev/">SvelteKit</a>, we now know how someone will be building their Svelte app. There could be a way to integrate runtime accessibility checks into the default template. Adding these checks would massively increase the kinds of issues Svelte could detect. I put in a <a href="https://github.com/sveltejs/kit/issues/1265">SvelteKit issue</a> suggesting that.</p>
<h3>Merge existing PRs</h3>
<p>There are quite a few a11y warning PRs open. Merging these PRs would improve Svelte's existing accessibility tooling.</p>
<ul>
<li><a href="https://github.com/sveltejs/svelte/pull/5852">role-has-required-aria-props</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/5955">noninteractive-roles-on-interactive-elements</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/5073">click-events-have-key-events</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/5361">no-redundant-roles</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/6693">no-nointeractive-tabindex</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/6652">click-events-have-key-events</a></li>
<li><a href="https://github.com/sveltejs/svelte/pull/6316">valid-aria-proptypes</a></li>
</ul>
<h3>Documentation</h3>
<p>I also think Svelte could improve its documentation around accessibility. Currently, it only lists the available <a href="https://svelte.dev/docs#a11y-label-has-associated-control">accessibility warnings</a>. <a href="https://reactjs.org/docs/accessibility.html">React,</a> <a href="https://v3.vuejs.org/guide/a11y-basics.html">Vue,</a> and <a href="https://angular.io/guide/accessibility">Angular</a> all have dedicated accessibility sections in their docs. These sections detail various considerations, practices, and testing around accessibility. Svelte could do the same, or at least link out to further resources. This would help developers better understand what they're responsible for.</p>
<h3>Foster a culture of accessibility</h3>
<p>This isn't necessarily on Svelte itself, but on the community. As Svelte developers and content creators, we should take care to make sure what we put out there is accessible, whether that's a blog post, a video, sharing a REPL, or creating a package. Sharing inaccessible demos (for example, a demo that uses <code>&lt;div on:click&gt;</code> instead of <code>&lt;button&gt;</code>) results in people copying that code for their projects and excluding some of their users. We can do better.</p>
<h2>Wrapping up</h2>
<p>As a developer, you should take ownership of the accessibility of what you build. Releasing an inaccessible app or site does harm to your users. The Svelte compiler will help you write accessible markup, but it won't catch everything—you still need to test your sites for accessibility in other ways. You don't have to be an expert, but you do have to care.</p>
<p>Here's some things you can do that will make a difference:</p>
<ul>
<li>Run other checkers like <a href="https://www.deque.com/axe/devtools/">Axe,</a> <a href="https://wave.webaim.org/">WAVE,</a> or <a href="https://web.dev/lighthouse-accessibility/">Lighthouse</a> on your webpage.</li>
<li>Make sure you can navigate your site only using your keyboard.</li>
<li>Continue learning about accessibility.</li>
<li>Learn how to use a screen reader.</li>
</ul>
<p>I'll link some introductory a11y resources down below, and feel free to reach out to me on Twitter or in the Svelte Discord if you have any questions.</p>
<blockquote>
<p>We can help educate developers about a11y and make a strong statement about the kind of web we want to be a part of — I think we should.</p>
<p>— <a href="https://github.com/sveltejs/svelte/issues/374">Rich Harris</a></p>
</blockquote>
<h2>Further a11y resources</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility">MDN</a></li>
<li><a href="https://www.a11yproject.com/">The A11y Project</a></li>
<li><a href="https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/">Smashing Magazine</a></li>
<li><a href="https://www.accessibility-developer-guide.com/">Accessibility Developer Guide</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICWx0C9LVWWVqvHlYJyqw7g">A11ycasts with Rob Dodson</a></li>
</ul>
<p><em>Thanks to <a href="https://www.swyx.io/">swyx</a> for discussing these ideas with me back when this was a rejected Svelte Summit talk.</em></p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Transitional Apps Transcription (Rich Harris @ Jamstack Conf 2021)</title>
    <link href="https://geoffrich.net/posts/rich-harris-jamstack-conf-2021/"/>
    <updated>2021-10-07T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/rich-harris-jamstack-conf-2021/</id>
    
    <content type="html"><![CDATA[
      <p>This is a transcription of Rich Harris' talk at <a href="https://jamstackconf.com/">Jamstack Conf 2021</a>. I've also added links to related content or topics as appropriate. You can watch the recording on <a href="https://www.youtube.com/watch?v=860d8usGC0o">YouTube</a>, and I highly recommend you do so—Rich is an engaging speaker, and there's some visual elements I couldn't capture in the text.</p>
<p>I made this transcription to make the content easier to reference by myself and others. If Rich Harris or the conference organizers would like me to take this down for any reason, please DM me on <a href="https://twitter.com/geoffrich_">Twitter</a>.</p>
<hr>
<p>There's an active debate happening in front-end circles about the right way to build websites, and like most front-end debates, both sides are really attacking a caricature of the other. On the one hand, we've got advocates for what is often referred to as &quot;modern web development.&quot; On the other hand, we have people who look at the state of modern mode development and argue that it's time for a bit of a &quot;come to Jesus&quot; moment about the path that we're on.</p>
<p>For brevity I'm going to call these camps the modernists and the traditionalists, but I don't want you to read any judgment into those terms. My goal in this talk is to try and tease out some of the claims and counter-claims and present what I think is going to become an increasingly popular approach to web development over the next few years. The debate is often reduced to MPA, or multi-page app, versus SPA, or single-page app. That framing really doesn't do either side justice, but nevertheless it's a good place to start.</p>
<p>A multi-page app is really just a website. When you go to that site, after all the DNS stuff happens, you connect to a server or a CDN in front of the origin server and it sends you some HTML. That HTML might have been dynamically generated for that specific request, it might be a cached version of a response that was generated earlier that day, or it might come from a static file server that was updated the last time the app was. Somewhere in that HTML perhaps there's a link. If you click it, the same thing happens again. The browser connects to a server, gets some more HTML, and when it gets enough of a response the existing page is removed and the browser starts rendering the new page.</p>
<p>This is a pretty simple model that has worked well for a long time. But as you all know, things have changed a bit over the last couple of decades. Browsers have got a lot more capable, user expectations have risen, and we find that a lot of sites need client-side interactivity, which means JavaScript. The more complex the application, the more JavaScript you're going to need. And so if you're building a multi-page app, you might find yourself in a bit of an opposition, because the minute you have any client-side updates you're actually building two apps: one for the initial render, that might be PHP or Rails or even handoff.html, and another app for subsequent updates that happen in JavaScript. Two apps, written in two languages, possibly by two different teams, that are nevertheless very tightly coupled to each other.</p>
<p>I could be wrong, but I'm pretty sure the web is the only place where that's considered remotely normal. And so a few years ago, people started building single-page apps. Instead of the content coming down the wire as HTML, you'd get a blank page with a script tag that loaded the code that would render the app. Now if that rendered content included a link, clicking on it wouldn't necessarily result in a round trip back to the server. In some cases you might need to get a little bit more JavaScript or a little bit more data, but either way, it's going to update the page in place rather than nuking everything, which means that navigation is going to feel instantaneous (or dare I say, app-like).</p>
<h2>SPAs are terrible</h2>
<p>There's just one problem with single-page apps: they're terrible. Here's a non-exhaustive list of things people will tell you are terrible about SPAs: you will need to load a bloated JavaScript framework (it's always bloated, of course); the performance is worse than a multi-page app; it will probably be buggy; accessibility will suffer; your code base will sit atop a teetering pile of duct-tape tooling (Webpack, Babel, all that other stuff); and it's less resilient—it won't work without JavaScript.</p>
<p>Unfortunately, these criticisms are largely valid. [Showing Instagram] This is one of the most popular single-page apps in the world. If JavaScript fails or is disabled, this is all you get [a blank page with the Instagram logo]. If it works, then after you've downloaded a literal megabyte of JavaScript (that's gzip, by the way—the amount of code the browser has to actually parse and evaluate is closer to five megabytes), you can look at a profile page containing... text and images. If you click on one of those images, it'll create a new history entry, but if you then click the back button, the app doesn't navigate. Now, I don't want to overstep the mark here. I'm not privy to the technical constraints and business requirements that led to these choices, but at the same time—come on, people. If the best front end engineers in the world can't make text and images work without five megabytes of JavaScript, then maybe we should just give up on the web platform.</p>
<p>People sometimes look at you askew if you suggest that most websites should be functional without JavaScript, but JavaScript failing is a fact of life. I often send people to <a href="https://kryogenix.org/code/browser/everyonehasjs.html">Everyone has JavaScript, right?</a> by <a href="https://www.kryogenix.org/">Stuart Langridge</a> to illustrate why, and JS-less users are systematically underrepresented in analytics because analytics tools use JavaScript. Also, many single-page apps violate your expectations of the browser's behavior in a way that's at best disorienting, and at worst exclusionary to people with accessibility requirements.</p>
<p>Normally on the web, if you middle click or command click a link it will open in a new tab. On this <a href="https://www.seamless.com/">food delivery website</a> that's ignored. It will navigate in the current page instead. If you then click the back button the layout will jump around for a bit before a nausea-inducing scroll back to where you were. There's lots of these little accessibility details that SPAs often get wrong—focus management, scroll management, navigation announcements, page titles, command click behavior—that collectively make the entire web a less predictable and less accessible medium. We shouldn't accept that. I pick real world examples, albeit more or less at random, because we need to reckon with the fact that single-page apps have kind of ruined the web.</p>
<h2>The problems SPAs solve</h2>
<p>So the backlash to modern web development is understandable, but it's important to remember that SPAs do in fact solve some real problems with the traditional approach. They also give you new capabilities. [Showing music library app] Here's something you can't do in a traditional app—you can't navigate from one page to another while continuing to play media. In an SPA, that's extremely straightforward.</p>
<p>Here's another: you can't use client-side state management that persists across navigations. [Showing email app] In this app, the first load contains a subset of my data. If I scroll, I load more. If I then click into one of these items then click back, I should be at the same place in the list, even though a fresh page load would exclude everything except the first tranche of results. This sort of thing is a little tricky to pull off in a single-page app, but it's essentially impossible in a multi-page app.</p>
<p>[Showing calendar app] Or consider transitions. Native app designers understand the importance of motion and object constancy in user interfaces, but on the web we tend to teleport instantly from one place to another—not because it's better, but because that's all browsers are capable of. In a single-page app, we can change that. I should note that there's a proposal in the works to add <a href="https://github.com/WICG/shared-element-transitions">navigation transitions</a> to the platform and it often gets brought up in these conversations, but look. I'm glad that it's happening, but don't imagine for a moment that it'll be as powerful as single-page app transitions can be.</p>
<p>Finally, something that very often gets overlooked whenever we talk about performance and JavaScript load is that the main culprit isn't front end frameworks: it's shitty ad tech and other third-party JavaScript. In a single-page app, you only have to load those lousy scripts once. In a multi-page app you have to load them for every single navigation. Even though the scripts are hopefully cached, you still have to evaluate them on every page load, which gives them plenty of opportunity to block the main thread and degrade the user experience.</p>
<h2>Comparing MPAs and SPAs</h2>
<p>So let's look at some of the pros and cons of these two approaches side-by-side. In particular, let's look at the MPA advantages.</p>
<p>[The following was not spoken, but appeared on a slide]</p>
<p>MPA advantages:</p>
<ul>
<li>Server-rendered (or static file, etc) - fast initial load</li>
<li>Resilient - works without JavaScript by default</li>
<li>Consistent experience with accessibility features built in</li>
<li>Use whatever technology you like</li>
</ul>
<p>SPA advantages:</p>
<ul>
<li>Single codebase</li>
<li>Fast navigation</li>
<li>Persistent elements</li>
<li>Client-side state management</li>
</ul>
<p>MPA disadvantages:</p>
<ul>
<li>Two apps instead of one</li>
<li>Navigation can be sluggish</li>
<li>JS (including shitty third party JS) must be evaluated on every page load</li>
</ul>
<p>SPA disadvantages:</p>
<ul>
<li>Lack of resilience</li>
<li>Too much JavaScript</li>
<li>Typically poor initial page load performance</li>
</ul>
<p>[Okay, back to the talk]</p>
<p>You probably already know that most modern frameworks support server-side rendering. If you're building everything by hand, then you might have a bit of a hard time setting everything up, but if you're using a so-called &quot;meta-framework&quot; like <a href="https://nextjs.org/">Next</a> or <a href="https://nuxtjs.org/">Nuxt</a> or <a href="https://kit.svelte.dev/">SvelteKit</a>, then you get that behavior out of the box, so you don't need to sacrifice that fast initial load.</p>
<p>Similarly, if an app uses server-side rendering instead of delivering an empty shell, it will have the same resilience as a traditional app. Your content will be universally available, and as long as you're using links and forms correctly, you can even have JavaScript-free interactivity just the same as if you're using links and forms in hand-authored HTML.</p>
<p>What about accessibility? If you're manually implementing navigation logic and so on then you'll probably end up making mistakes here, but again, modern meta-frameworks take this stuff pretty seriously.</p>
<p>The one big remaining thing is language choice. If you're already part of the anti-JavaScript resistance then nothing I say in the rest of this talk is going to matter that much, but I'm going to get into this later: that ship might have sailed. I do need to take a moment to shout out to projects like <a href="https://hex.pm/packages/phoenix_live_view">Phoenix LiveView</a>, <a href="https://docs.stimulusreflex.com/">StimulusReflex</a>, and so on, which are solving the two code bases problem from the other end by letting you write code that pushes DOM updates from a server over WebSockets. I'm a little skeptical about just how far you can push that model, but it's a pretty cool field of experimentation.</p>
<h2>What's better than a spa?</h2>
<p>So we can build apps that combine the best aspects of traditionalism and modernism: a fast initial load, accessibility, resilience, instant navigation, a cohesive code base, and capabilities that used to be out of reach. What should we call them? Well, we already have a sea of acronyms that we use to describe all these various techniques, so at the risk of being all <a href="https://xkcd.com/927/">xkcd 927</a>, maybe there's a new acronym that we could invent. What's better than a spa?</p>
<p>HTML Optimized Through Techniques Users Believe in.</p>
<p>Super Awesome Usable Neato Apps.</p>
<p>Better Applications Through HTML Hyper-Optimized Using Scripts... Etc.</p>
<p>JavaScript Application Centered on Usability, Zippiness, and Zen-Inducement.</p>
<p>Okay, these are all terrible. I'm sorry: I really thought that inspiration would strike and I would be able to come up with something in time for the conference, but that didn't happen, so I started googling to see if there's a word for the synthesis of traditionalism and modernism, and it turns out that the interior design community has thought about this. They call it &quot;transitional design.&quot;</p>
<p>I'm going to read a paragraph from <a href="https://www.apartmenttherapy.com/transitional-design-36765367">ApartmentTherapy.com</a>:</p>
<blockquote>
<p>Whereas traditional design can sometimes feel prim and stuffy, and modern design can lean too heavily on the sleek and streamlined look, transitional design samples elements from each aesthetic to form an equally classic and fresh feel. Think of transitional design as having the best of both worlds.</p>
</blockquote>
<p>They could be talking about web development. I actually really love this, and not just because I'm a sucker for interior design porn. There's an obvious linguistic connection to the kinds of transitions I was talking about earlier. For too long, we've modeled web apps as discrete pages that you jump to, rather than cohesive spaces that you move around. And it's not because one mode is universally more appropriate than the other; it's because our thinking has been constrained by the medium. I often recommend this website, <a href="https://www.hudsandguis.com/">HUDS+GUIS</a>, where motion designers for TV and film imagine what user interfaces could look like if we were freed of our technological constraints. I want a web with more design freedom, and transitions are a big part of that.</p>
<p>But leaving all that aside, this word &quot;transitional&quot; resonates with me. It's a humble word that recognizes that we're in a constant state of evolution. It doesn't pretend to have all the answers, but it promises that we're going to keep seeking them. It looks towards the future, but it's respectful of the past. In short, it's everything we should aspire to be.</p>
<p>So, Jamstack Conf, let's coin a new term: <a href="https://twitter.com/search?q=%23transitionalapps">#transitionalapps</a>. Let's see if we can get this hashtag trending. In fact, you know what? I think I just figured out the title for this talk.</p>
<h2>On HTML Over The Wire and GitHub</h2>
<p>I want to talk a little bit about what transitional apps look like in practice, particularly as it relates to <a href="https://kit.svelte.dev/">SvelteKit</a>, which is a meta-framework we're currently building. But first I need to talk to the eye rollers in the audience, because I guarantee there's a few of you.</p>
<p>Some people claim that you can get the benefits of single-page apps without writing JavaScript. The thing in Rails circles at the moment is HTML Over The Wire, or <a href="https://hotwired.dev/">Hotwire</a>, which is a truly fantastic name. The idea is that the state and the rendering logic live on the server, but you make the app more like an SPA by sending partial chunks of HTML instead of complete pages whenever there's an update. The marketing page says this makes you more productive without sacrificing any of the speed or responsiveness associated with the traditional single-page application: citation needed.</p>
<p>Look, I think this is a really cool idea, but I'm not totally convinced it works in practice. It turns out it's really hard to have things like optimistic updates when the rendering logic lives on the server, so the responsiveness of your app is effectively dictated by network latency. I don't want to be too critical of this idea because it's a good fit for a certain class of application, but I do think we need to be honest about its limitations.</p>
<p>It's not Hotwire but a Rails app that often gets mentioned in these conversations and uses a similar technique is <a href="https://github.com/">GitHub</a>. I love GitHub. I rely on GitHub, and back in the day it was one of the first big applications that used the History API to do client-side navigation and it was a real wow moment, but the front end is super buggy.</p>
<p>Let's say you go to your issues list and click on one of the unreads. You decide you don't want to deal with it right now, so you back out. Hang on a minute, it's still got the blue unread marker. Refresh the page and it's fixed, fine. Actually let's close that issue. Hang on, we still have an open issue—or do we?</p>
<p>It turns out that when you send partial HTML updates instead of having the rendering logic and the state live in the same place, you get inconsistencies everywhere. I've seen PRs that have both the green open lozenge and the purple merged lozenge on the same page. It's incredibly disorienting to the extent that I obsessively refresh every GitHub page after navigation almost as a nervous tic.</p>
<p>Another example: if you try and interact with the page while actions are running, there's a good chance that it will go haywire. Why? Because we're sending partial HTML updates instead of data. This kind of fragility is essentially baked into this development model. You can fix it, but you'll always be fighting an uphill battle.</p>
<h2>Documents versus apps</h2>
<p>Another common objection is that documents and apps are fundamentally different and it's senseless to use app development tools to build document sites. I definitely agree with the sentiment that you should use the right tool for the job, but I want to question the underlying premise here. Look at this product page from an e-commerce site: is it a document or an app? Clearly it's a little bit of both. It's mostly text and images, but it also has buttons that do stuff like &quot;add to cart.&quot; My day job involves building <a href="https://www.nytimes.com/interactive/2021/us/covid-cases.html">interactive widgets</a> that live on New York Times article pages. News sites are classic examples of the document-based web, but this pretty clearly has app-like characteristics.</p>
<p>We talk about documents versus apps as though there is a dichotomy, but it's not: it's a spectrum. When we erase the stuff in the middle we do the web a great disservice. It's a medium that by its very nature resists definitional boundaries.</p>
<p>Now of course you can point to the extremes on the spectrum and say they don't count. But there's no reason your personal blog shouldn't have instant navigation, for example, and if you wanted to add a video player containing your conference talks that people could watch as they continue to navigate around your site, then you shouldn't have to throw away your old stack and begin afresh with a new foundation.</p>
<h2>Edge computing</h2>
<p>A lot of people won't be persuaded by all this because the real reasons for the anti-modern web development backlash aren't technological, they're cultural. Some folks just really don't want to use JavaScript or JS-adjacent technologies. It's like, I get it. I don't agree with it—I think JavaScript is actually pretty great—but I do understand it. But like it or not, the trends are in JavaScript's favor and there's one in particular that I think is going to become especially relevant over the coming years: that's edge computing.</p>
<p>We've already got things like <a href="https://workers.cloudflare.com/">Cloudflare Workers</a>, Netlifly (sic) edge handlers... Netlifly. Netlify.</p>
<p>We've already got things like Cloudflare Workers, <a href="https://www.netlify.com/products/edge/edge-handlers/">Netlify Edge Handlers</a>, <a href="https://deno.com/deploy/">Deno Deploy</a>, and we're going to see more entrants in this space. What these platforms let you do is run code cheaply, close to where the user is, with none of the cold start headaches that you associate with lambda, and none of the maintenance or scaling concerns that come with running servers.</p>
<p>What they all have in common is that they're all built on <a href="https://v8.dev/">V8</a>. Because of WASM you can theoretically run just about anything at the edge, but realistically I think we can expect JavaScript to have a significant home team advantage. Transitional apps—remember to use the hashtag—are really well placed to take advantage of this trend because they have another really interesting characteristic: they can transition between different environments depending on what's most appropriate. Whether that's on your server or in the cloud or at the edge or on the user's device, be that on the main thread or in a service worker or web worker.</p>
<h2>Too much JavaScript</h2>
<p>You might think this all sounds like too much JavaScript and that brings us to the final big objection, which is that modern websites serve too much JS to the browser. As we talked about earlier, the biggest culprit when it comes to JavaScript bloat is usually all the third-party script, and modern web apps actually have a very significant advantage here because they can amortize the cost over the duration of the session rather than paying in full on every navigation.</p>
<p>But there is some real truth here. If you have a server-rendered page and you're hydrating it with an interactive client-side app then you will end up serving data and component code that isn't strictly necessary. This is probably the least compelling part of the modern web story right now, but it's a very active area of research and development.</p>
<p>The React team is working on <a href="https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html">server components</a>, which is sort of like HTML over the wire except vastly more sophisticated. <a href="https://markojs.com/">Marko</a> is doing something called partial hydration, which means skipping the code and data for non-interactive subtrees. <a href="https://github.com/BuilderIO/qwik">Qwik</a> is aggressively lazy-loading everything, so you don't load code until you need it for a specific interaction. <a href="https://astro.build/">Astro</a> is tackling this problem with so-called <a href="https://docs.astro.build/core-concepts/component-hydration">&quot;islands architecture&quot;</a>, which is less granular than Marko but gets you most of the way there. <a href="https://svelte.dev/">Svelte</a>, the framework I help maintain, uses a compiler to make the cost of the framework as low as possible, and though we don't yet do any kind of partial hydration you can easily <a href="https://kit.svelte.dev/docs#ssr-and-javascript-hydrate">turn off hydration</a> at the page level.</p>
<h2>SvelteKit demo</h2>
<p>And I guess that's as good a segue as any into a little demo of what we on the Svelte team have been working on recently. SvelteKit, our meta-framework, is essentially a toolkit for building transitional apps (hashtag). If you want to follow along at home, please do.</p>
<p>First we do <code>npm init svelte@next</code>. Once we hit a stable release soon, it'll just be <code>npm init svelte</code>. Follow the prompts and your project gets scaffolded to the current directory or whichever directory you specify. Install dependencies, then run <code>npm run dev -- --open</code> to start the development server and open the app in a new tab.</p>
<p>This is a simple <a href="https://netlify.demo.svelte.dev/">demo app</a>. This page has a little dash of client-side interactivity, just so you can check that the server-rendered HTML hydrated as expected. If we follow this instruction and edit <code>src/routes/index.svelte</code>, we can see what happens if we edit the source code: it updates the page immediately using <a href="https://vitejs.dev/">Vite's</a> hot module reloading. In fact, if we turn on VS Code auto saving and turn the delay right down, we can see that hot module reloading keeps up with our keystrokes, though I don't necessarily recommend it.</p>
<p>If you're editing styles, then component state will even be preserved, which makes it super easy to tweak the design of your site. If we do <code>npm run build</code> followed by <code>npm run preview</code> we can look at a production build of the site. Let's navigate to the <a href="https://netlify.demo.svelte.dev/about">about page</a>. Unlike the home page, there's no client-side interactivity needed here so we're able to render this page without any JavaScript at all. We can check that by viewing the page source: all markup, no script, just as it should be. And because this page is marked as pre-renderable in the source code, when we actually deploy this app, whether we deploy it as a Node server or with Netlify or Vercel or AWS or Cloudflare Workers or Deno or whatever else, requesting this page won't involve any server-side rendering. It'll just serve a static file in most cases via CDN without you even needing to configure anything. Same is true for the home page in fact.</p>
<p>Finally, if we go over to our <a href="https://netlify.demo.svelte.dev/todos">todos page</a>, we can start adding some reminders: do this, do that, do more stuff. If we hit this page directly, it's dynamically server rendering with our data, so we can't pre-render it. Importantly, if we disable JavaScript, this page still works because we're using progressively enhanced form submissions to send data back to the server.</p>
<p>This is the briefest possible overview of a SvelteKit app, but let's recap. We have a mix of static pre-rendering with client-side interactivity, a pre-rendered page with zero JavaScript, a dynamically server-rendered page with interactivity that works without JavaScript but upgrades the user experience with JavaScript, an API endpoint that powers that page, and we can deploy all of this using a combination of edge functions and CDN-served static files without having to administer any of it, all from a single code base with an unbelievable development experience powered by Vite. This is what transitional apps are all about and whether or not that name catches on (hashtag), the ideas are here to stay.</p>
<p>So the next time you hear people saying that multi-page apps are best or single-page apps are best remember: the truth is way more nuanced and it's way more exciting. Thank you for watching.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Notes on Rich Harris&#39; PodRocket interview</title>
    <link href="https://geoffrich.net/posts/rich-harris-podrocket/"/>
    <updated>2021-10-05T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/rich-harris-podrocket/</id>
    
    <content type="html"><![CDATA[
      <p>LogRocket's podcast <a href="https://podrocket.logrocket.com/">PodRocket</a> released a <a href="https://podrocket.logrocket.com/rich-harris">new episode</a> with Rich Harris, the creator of <a href="https://svelte.dev/">Svelte</a>, this morning. There were so many good insights in it that I wanted to pull out some choice quotes and take some notes to share this in a non-audio medium.</p>
<p>I highly recommend listening to the full episode, especially if you're new to Svelte. Even if you're already familiar with Svelte, there are still some great insights to Svelte's philosophy and its position in the current framework landscape.</p>
<p>All these quotes are from Rich Harris. I pulled them from the <a href="https://assets.fireside.fm/file/fireside-images/podcasts/transcripts/3/3911462c-bca2-48c2-9103-610ba304c673/episodes/8/8a485d85-aea7-4811-86c6-de00f8399413/transcript.txt">transcript</a> of the episode and cleaned them up a bit, though the emphasis is mine.</p>
<h2>Svelte's advantages</h2>
<p>Svelte's advantage is that it lets you write your components declaratively, and then translates it into the underlying DOM manipulation at compile time. This typically makes for smaller bundle size and faster state updates.</p>
<blockquote>
<p>Instead of imperatively manipulating the DOM with <code>document.createElement</code> and all of the things that are provided by the platform. It puts a declarative layer on top of that so that you can describe the output that you want, and then the framework's job is to translate that into the underlying DOM manipulations.</p>
<p>It generates typically very small code, which means that your application is going to load very quickly, and the updates, when you have a state change within your application, also take place very quickly, because it doesn't need to regenerate your entire application. It's just surgically updating the part of the page that's affected.</p>
<p>I should say at this point, because this is what I've been telling people about Svelte since it first came around in 2016, the landscape has actually shifted a little bit and other frameworks are, in many cases, becoming a little bit more Svelte-like. So when I talk about these unique advantages, they're not so unique anymore. <strong>Svelte's selling point these days is really more about the developer experience it provides.</strong> Vue, in particular, has adopted a lot of these techniques and also has some of those bundle size and performance characteristics.</p>
</blockquote>
<h2>Just JavaScript vs a DSL</h2>
<p>There are advantages to staying close to the platform and only using syntax that exists natively, but domain-specific languages (DSLs) have advantages too.</p>
<blockquote>
<p>One of the things I say from time to time is that DSLs are actually a good thing. People on the other side, on the just JavaScript side, will be like, &quot;I don't want to learn a domain specific language,&quot; because they've been bitten by domain specific languages in the past. But, actually, <strong>why wouldn't you want the language to be specific to the domain that you're solving.</strong> As long as the DSL doesn't decrease the amount of flexibility that you have, then other things being equal, it's probably a good thing. If it enables you to express the ideas in your application more concisely and more consistently, then it's probably a good thing.</p>
</blockquote>
<h2>Svelte's primary language is HTML</h2>
<p>Svelte's component syntax uses HTML as a base—the fundamental language of the web.</p>
<blockquote>
<p>The thesis here is the HTML is the language of the web. JavaScript is not the language of the web. JavaScript is one of the three languages that are sort of contained within HTML, because HTML is designed to contain CSS and JavaScript, whereas a lot of frameworks go the other way. They're like, &quot;JavaScript is the primary language and we're going to try and shoehorn HTML and, in some cases, CSS into JavaScript.&quot; <strong>Svelte takes this opposite view that you begin with HTML, and then you add JavaScript as necessary.</strong></p>
</blockquote>
<h2>On SvelteKit's flexibility</h2>
<p>SvelteKit is a framework built for the serverless front-end landscape.</p>
<blockquote>
<p>It's a full stack application framework that lets you build fully server rendered applications with all of the modern best practices, but it's also very flexible both in how you build your app. You can build a completely single-page app or you can build a completely JavaScript-free multi-page app or something in between, and you can vary it by page; but you can also deploy it to a bunch of different places if it's suitable. Like [if] you're building a very static content site, then you can bake it out of static HTML and then you can just throw that on GitHub Pages or whatever it is.</p>
<p>But if you're doing something that's highly dynamic, then you can have a server component or you can have serverless functions or you can put it inside a Cloudflare Worker, and you can pre-render the parts of your application that are static and dynamically render the parts that are not. And it's really a way of addressing all of the problems, or as many of the problems as possible, that you encounter while building a modern web application.</p>
</blockquote>
<p>For more on this topic, see the <a href="https://svelte.dev/blog/whats-the-deal-with-sveltekit">SvelteKit announcement post</a>, though some things have changed since that was written. Most notably, SvelteKit is now using <a href="https://vitejs.dev/">Vite</a> instead of Snowpack.</p>
<h2>What's on the Svelte roadmap?</h2>
<p>I liked hearing about what could be next for Svelte, though note that nothing on this list is set in stone. It seems like we can expect more iteration on Svelte core once SvelteKit hits 1.0 and is stable.</p>
<blockquote>
<p>I'm almost reluctant to talk about some of these things, because <strong>a lot of it is speculative and we're not really sure where some of these ideas are going to land.</strong> But we have this long wish list of things that we want to do for Svelte 4.0 and beyond.</p>
<p>Some of it is adopting some of the features that other frameworks have proven out, like <strong>streaming SSR</strong> and things like that. Some of it is around <strong>speeding up our own compiler</strong>, so that the feedback loop gets even tighter. Changing how the compiled output is generated such that if you have a very large application, you're <strong>paying only a very tiny incremental cost per component</strong>. We have some grand ideas about better ways to think about <strong>motion inside user interfaces</strong> and how that should be tied to the core of a framework as opposed to left to a userland solution.</p>
<p>We have a great many ideas, and I don't want to give them all away. But there's a lot on our plate, and we're pretty excited to get stuck into it when we can.</p>
</blockquote>
<h2>Does Svelte have more &quot;magic&quot; than something like React?</h2>
<p>A common perception is that Svelte has more magic as opposed to a framework like React. However, React has magic too—the difference is that Svelte's magic happens at compile time, while React's magic happens at runtime.</p>
<blockquote>
<p>I don't know that that [magic vs explicit is] such a real dichotomy. The differences that Svelte is doing the stuff at build time. It's changing your expectations of how JavaScript works, because it's intercepting assignments and turning those into reactive state changes. React is doing something similar.</p>
<p>If you look at a React function and you don't know what's happening with hooks, then you would look at that code and you'd be very confused about what's happening, because the functions behave in ways that JavaScript functions generally don't. The fact that the return value of a function depends on how many times you've called that function and whether you've called the return value in the past and things like that, that's deeply weird, it's not how JavaScript works at all. You can implement it with JavaScript, but it's not how JavaScript works. They are violating expectations on a fairly fundamental level.</p>
<p>The difference is that they're doing it all at runtime instead of having all of that stuff happen behind the scenes at compile time. And I tend to think that focusing on whether the magic happens at build time or whether it happens at runtime is focusing on the wrong thing. <strong>All frameworks involve magic. Svelte is just trying to do the expensive bits of magic before that code gets to the user. That's the only real difference.</strong></p>
</blockquote>
<h2>On the &quot;framework wars&quot;</h2>
<p>Treating web development as a war between one framework or another is not healthy. The people building these frameworks get along and are all trying to improve front-end development and the web as a whole. There is no One True Framework, and different frameworks fill different needs.</p>
<blockquote>
<p>I dislike the framework wars framing generally, because what might not be obvious to a lot of people who are users of these frameworks as opposed to actively involved in the development of them, is that... The people who are building these frameworks, we often talk to each other. We by and large know each other. We get along. There's no animosity There's no warring whatsoever.</p>
<p>We hang out in the same spaces and we share ideas with each other, and really we're all, I think, just trying to collectively advance the state of front end development by focusing on the little bits of innovation that we can contribute, and then gradually, all of that stuff filters through the ecosystem and gets shared more widely. And Svelte occupies a part of the landscape that is attractive to a certain kind of developer. React occupies a different part of the landscape. <strong>They're both completely fine solutions for the people who are choosing them, and they're not going to be right for everyone and that's fine.</strong></p>
</blockquote>
<h2>Svelte is no longer an underdog</h2>
<p>Svelte is now talked about as one of the &quot;big 4&quot; frameworks. At this point, we can't really consider it an underdog, since so many are aware of it. There are many frameworks with much less name recognition than Svelte.</p>
<blockquote>
<p>So are we the underdog? It depends on what you're looking at. There's three big frameworks, right? There's React, Angular and Vue. And if you had to pick a fourth framework, I think most people at this stage would probably say the fourth framework was Svelte. And so if underdog means that you're inside the top four instead of the top three, then absolutely are the underdog.</p>
<p>At the same time, we have a pretty good mindshare at this point. A lot of people in the front end world have heard of Svelte. A lot of people talk about Svelte. There's a whole industry of people doing YouTube videos about Svelte. <strong>And I think calling ourselves underdog would be a disservice to the people who are actually laboring on open source projects without getting a whole lot of recognition.</strong> I think we have plenty of recognition. And so, I wouldn't claim the underdog label for ourselves by any stretch, but nor are we in any way mainstream.</p>
</blockquote>
<h2>See the podcast for more, including</h2>
<ul>
<li>How Svelte compares to the virtual DOM (see also: <a href="https://svelte.dev/blog/virtual-dom-is-pure-overhead">Virtual DOM is pure overhead</a>)</li>
<li>The ideal use case for Svelte</li>
<li>Svelte's two-pronged approach to managing state</li>
<li>How templating works in Svelte</li>
<li>Whether a React-specific DSL could help reduce its boilerplate (see also Rich's <a href="https://twitter.com/Rich_Harris/status/1438502561942810625?s=20">tweet</a> on the subject)</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Exploring Marvel Comics&#39; history with SvelteKit</title>
    <link href="https://geoffrich.net/posts/marvel-unlimited-by-year/"/>
    <updated>2021-10-03T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/marvel-unlimited-by-year/</id>
    
    <content type="html"><![CDATA[
      <p><em>tl;dr I launched a <a href="https://marvel.geoffrich.net/">new site</a> where you can see Marvel comics published in a <a href="https://marvel.geoffrich.net/year">given year</a> and retrieve a <a href="https://marvel.geoffrich.net/comic/random">random comic</a> available on the Marvel Unlimited app. The code is <a href="https://github.com/geoffrich/marvel-by-year">open source</a> on GitHub.</em></p>
<p>On September 9, the Marvel Unlimited app (MU), which lets you read tens of thousands of Marvel's comics for a monthly fee, got a huge update. While it added some long awaited features such as unlimited downloads, it also wreaked havoc on users' reading history and libraries and removed some beloved features.</p>
<p>One of these features was the ability to sort by date—for example, being able to view every comic released in 1993. This was crucial for those trying to read every Marvel comic in chronological order, as well as those following a yearly comic book reading club like <a href="https://www.comicbookherald.com/my-marvelous-year/">My Marvelous Year</a>.</p>
<p>I was one of those users disappointed by the feature's removal. After I discovered that all the data needed to re-create the feature was available from <a href="https://developer.marvel.com/">Marvel's API</a>, I started coding. A few weeks later I launched <a href="https://marvel.geoffrich.net/">Marvel Unlimited by Year</a>.</p>
<h2>Features</h2>
<img src="/images/marvel-unlimited-by-year/1977-comics.png" alt="Screenshot of the 1977 comics page. The first three titles are shown: Champions #10, Thor #255, and Incredible Hulk #207" title="Comics available on MU published in 1977">
<ul>
<li>View all comics available on Marvel Unlimited for a given year. For example, here's <a href="https://marvel.geoffrich.net/year/1975">1975</a>.</li>
<li>Each comic's cover links directly to the issue in Marvel Unlimited or the web-based reader, depending on your device.</li>
<li>Sort and filter the results by series, creator, or event.</li>
<li>View a <a href="https://marvel.geoffrich.net/comic/random">random selection</a> of available comics, or random comics released in a specific decade. The old app had a button that would give you a random comic, though it wouldn't allow you to specify the decade. The MU team have stated that they <a href="https://www.reddit.com/r/MarvelUnlimited/comments/py12lt/hi_mu_subreddit_were_the_team_behind_marvel/herewi5/">don't have plans</a> to add the random button to the new app, so I'm glad I was able to make it available on my site.</li>
</ul>
<img src="/images/marvel-unlimited-by-year/random-comics.png" alt="Screenshot of the random comics page. 6 covers are shown: Dr. Strange Sorcerer Supreme, Star Wars Dark Empire II1, Marvel Digital Holiday Special, History of the Marvel Universe, New X-Men, and Luke Cage: Power Man." title="The random comic page">
<h2>The tech stack</h2>
<ul>
<li><a href="https://svelte.dev/">Svelte</a> and <a href="https://kit.svelte.dev/">SvelteKit</a> for the app framework. This was my first major project in SvelteKit and I had a great experience. Svelte is my favorite front-end framework to work in, and SvelteKit builds a full-stack app framework on top of it with SSR, server endpoints, and routing, as well as a fast dev environment powered by <a href="https://vitejs.dev/">Vite</a>. Despite it still being pre-1.0, I had very few issues and I'm looking forward to moving some of my work projects over to SvelteKit as soon as possible.</li>
<li><a href="https://www.typescriptlang.org/">TypeScript</a> on the server and client side. The complexity of the API response made auto-complete a must-have (and by extension, types). SvelteKit gave me the option to set this up automatically, so I didn't have to wrangle any configuration.</li>
<li><a href="https://redis.io/">Redis</a> cache hosted on <a href="https://upstash.com/">Upstash</a>, a serverless option where you pay per-request instead of per-server. Since I had a limited number of API requests per-day, I needed to cache the responses for 24 hours. The <a href="https://marvel.geoffrich.net/comic/random">random comic</a> functionality is also implemented using Redis queries.</li>
<li><a href="https://www.netlify.com/">Netlify</a> hosted the deployed site. So far, the traffic hasn't exceeded the limits of their free plan (125k function invocations).</li>
<li><a href="https://developer.marvel.com/">Marvel API</a> for the data. While the documentation doesn't seem like it's been updated recently (the last change announcement was 2014), it still works great and the data is current.</li>
</ul>
<h2>Testimonials</h2>
<p>I posted this project on the <a href="https://www.reddit.com/r/MarvelUnlimited/comments/pxe7l9/i_made_a_site_that_lets_you_browse_mu_by_release/">Marvel Unlimited subreddit</a> last week, and received a glowing response.</p>
<blockquote>
<p>Uh, I think I can speak for everyone when I say, holy cow this is great! Super fast and the link to the iOS app works great! I guess it’s true what they say, not all heroes wear capes. I wish I could upvote this multiple times!</p>
</blockquote>
<blockquote>
<p>Even better than the old app because it does exactly what I used to do, but it loads so much faster!</p>
</blockquote>
<blockquote>
<p>This is the best thing I've ever seen on Reddit.</p>
</blockquote>
<p>It was nice to develop something that others found useful! One of the best things about knowing how to code is being able to create something solving a niche problem that wouldn't otherwise get addressed. These comments also show that SvelteKit provides a speedy user experience as well as a great dev experience.</p>
<p>As a dev myself, I know as well as anyone that rewriting a product with an existing user base is hard. I'm not trying to disparage the development team as part of this effort—I just wanted to restore a feature that I (and many others) found essential, and try out some new dev tools in the process.</p>
<h2>Future plans</h2>
<p>I'm not done developing this site and still have some features I want to add, such as filtering by release month and listing out available series.</p>
<p>If you're interested, the code is <a href="https://github.com/geoffrich/marvel-by-year">open source</a> on GitHub. You can even run it yourself, though you'll need to provide your own Marvel API keys and Redis connection. Docs are sparse at the moment, though I hope to flesh them out eventually.</p>
<p>Stay tuned for a follow-up post with some of the things I learned on this project.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Can you scope styles without increasing specificity?</title>
    <link href="https://geoffrich.net/posts/svelte-scoping-where/"/>
    <updated>2021-09-27T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-scoping-where/</id>
    
    <content type="html"><![CDATA[
      <p><em>Or, alliteratively: Settling Svelte's style scoping specificity wars with :where</em></p>
<p>In my previous post on <a href="/posts/svelte-scoped-styles/">Svelte's style scoping</a>, I lied to you. When talking about how Svelte's style scoping can unexpectedly increase CSS specificity, I said:</p>
<blockquote>
<p>Svelte needs to add <em>something</em> to the CSS rules to make sure they only apply to a single component, and that will increase the specificity.</p>
</blockquote>
<p>That's not entirely accurate. The first part is true—Svelte does need to add <em>something</em> to the CSS. However, that doesn't need to increase specificity. In fact, there is a CSS pseudo-class that will <em>not</em> increase specificity: <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where</code></a>. It can be used like so:</p>
<pre class="language-css"><code class="language-css"><span class="token comment">/* :where takes a list of selectors */</span><br><span class="token selector">:where(h1, h2, h3) p</span> <span class="token punctuation">{</span><br>  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token comment">/* This targets the same elements as the following */</span><br><span class="token selector">h1 p,<br>h2 p,<br>h3 p</span> <span class="token punctuation">{</span><br>  <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Because <code>:where</code> has 0 specificity, the first rule in the example above has a specificity of 0-0-1 (the same as a single <code>p</code> selector), while the second has a specificity of 0-0-2 (since there are two element selectors).</p>
<p>So, how can <code>:where</code> improve Svelte's style scoping? First, let's review Svelte's current scoping method.</p>
<h2>A brief recap</h2>
<p>In case you missed (or forgot about) my <a href="/posts/svelte-scoped-styles/">previous post</a>, Svelte scopes component styles by adding a unique CSS class (such as <code>svelte-abcdef</code>) to the HTML elements inside the component and to the styles that target them. This ensures that styles you write inside a Svelte component only apply to elements inside that component. <a href="https://vue-loader.vuejs.org/guide/scoped-css.html">Vue</a> and <a href="https://github.com/vercel/styled-jsx">styled-jsx</a> use a similar technique.</p>
<pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- What you write in the Svelte component --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>This is a paragraph with scoped styles.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token comment">/* I only affect elements in this component */</span><br>  <span class="token selector">p</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- The resulting HTML/CSS --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>svelte-dvinuz<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>This is a paragraph with scoped styles.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">p.svelte-dvinuz</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>This method is very good at keeping styles scoped to a single component. However, you run into issues if you have global styles that you <em>also</em> want to apply to Svelte components. By adding a scoping class, Svelte <a href="/posts/svelte-scoped-styles/#heading-specificity-and-scoping">increases the specificity</a> of your component's styles, which may mean that global styles don't apply when you expect them to. In some cases, Svelte adds multiple scoping classes, increasing the specificity even more.</p>
<p>If you're interested in why and how Svelte does this, check out my article linked above, where I go into much more detail. But for now, I want to consider something: if the problem is that Svelte increases specificity unexpectedly, could we use <code>:where</code> to scope styles but <em>not</em> increase specificity?</p>
<h2>A different approach</h2>
<p>Instead of adding the scoping class directly, Svelte <em>could</em> wrap it in <code>:where()</code> instead:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">p:where(.svelte-dvinuz)</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>Because it is inside <code>:where</code>, <code>.svelte-dvinuz</code> does not increase the specificity of the original CSS rule. Instead of increasing the style's specificity to 0-1-1, it keeps the original specificity of 0-0-1. This means the rules you write in your Svelte component will more predictably interact with your global styles.</p>
<p>However, at the moment, I don't think this method is a drop-in replacement for Svelte's class-based style scoping:</p>
<ul>
<li><strong>Browser support could be better</strong>: while <code>:where</code> is supported in all evergreen browsers, that's only <a href="https://caniuse.com/mdn-css_selectors_where">86% of web users</a> globally at time of writing. This includes the basically-dead IE11, yes, but also a fairly-recent iOS version (13) and Samsung Internet (which has a similar market share to Firefox, according to Can I Use). In many cases, this would be sufficient support to try out new CSS features. However...</li>
<li><strong>There's no graceful degradation</strong>: because <code>:where</code> would be present in every CSS rule that came from a Svelte component, browsers that don't support it would ignore those styles entirely. Many apps would have no styles at all for users on older browsers. And unlike modern JS syntax, there's no way to polyfill <code>:where</code>, at least not without breaking the scoping behavior.</li>
<li><strong>Breaking change</strong>: because the specificity of styles in a Svelte component is reduced, some global styles would start applying where they wouldn't previously.</li>
<li><strong>Possible size increase</strong>: <code>:where(svelte-hash)</code> is more characters than <code>.svelte-hash</code>, though it is shorter than adding two scoping classes. In some cases, I would expect the CSS size to increase (though the impact on the generated CSS is likely minimal due to compression).</li>
</ul>
<p>For these reasons, if Svelte were to implement this kind of scoping, it would need to either be in a major version bump (Svelte v4?) or in a new configuration option that devs could opt-in to.</p>
<p>However, it's definitely a compelling proposition. Perhaps when browser support improves, using <code>:where</code> to scope styles will be a viable option. Until then, we'll have to live with occasional specificity battles for the price of scoped component styles.</p>
<p><em>Shout-out to <a href="https://www.reddit.com/r/sveltejs/comments/pjpaz6/how_svelte_scopes_component_styles/hc1dbpu/?context=3">iainsimmons</a> on r/sveltejs and <a href="https://github.com/sveltejs/svelte/issues/4374#issuecomment-921976465">Arkkimaagi</a> on GitHub for bringing up this idea and discussing it with me.</em></p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>How Svelte scopes component styles</title>
    <link href="https://geoffrich.net/posts/svelte-scoped-styles/"/>
    <updated>2021-09-06T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-scoped-styles/</id>
    
    <content type="html"><![CDATA[
      <p>By default, any styles you write in a Svelte component are scoped to that component. This means that the <code>p</code> selector in the following code will only apply to <code>&lt;p&gt;</code> elements inside this component.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>This is a paragraph with scoped styles.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token comment">/* I only affect elements in this component */</span><br>  <span class="token selector">p</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>But how does this scoping actually work? In this post, I'll explain how Svelte scopes styles to your components and the implications for global styles in the rest of your app. I think this topic is interesting on its own, but understanding Svelte's scoping method will also help you better debug your component styles.</p>
<p>This post is accurate for the Svelte version at time of writing (v3.42.4). However, the implementation of Svelte's style scoping is subject to change—in Svelte's lifespan, it has changed <a href="https://github.com/sveltejs/svelte/pull/607">several</a> <a href="https://github.com/sveltejs/svelte/pull/1192">times</a> <a href="https://github.com/sveltejs/svelte/pull/4146">already</a>—and I don't guarantee that this post will remain accurate.</p>
<h2>Classing up the joint</h2>
<p>When working on a Svelte app, you may have seen some unexpected CSS classes beginning with &quot;svelte-&quot; in the DevTools inspector. Why are those there? Svelte applies those classes to styled elements in your app so that component styles don't &quot;leak out&quot; to elements outside the component.</p>
<p>For example, the component in the previous section is transformed into the following.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>svelte-dvinuz<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>This is a paragraph with scoped styles.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">p.svelte-dvinuz</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>The transformed CSS rule won't apply to <code>&lt;p&gt;</code> elements outside of the component, because they won't have the <code>svelte-dvinuz</code> CSS class applied. Only elements inside the component will match the scoped CSS rule.</p>
<p>The class Svelte adds is not random. It is generated using a hash of the component's styles, making it unique for every component (unless two components styles are <strong>exactly</strong> the same).</p>
<h2>More complex rules</h2>
<p>Let's look at what happens when the CSS rules become more complicated. The following component uses a <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator">descendant combinator</a>. This is not strictly necessary in this example case (you could target <code>span</code> and <code>li</code> directly), but it's useful for illustration.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Apples <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🍎<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Bananas <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🍌<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Carrots <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🥕<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">ul li</span> <span class="token punctuation">{</span><br>    <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token selector">ul li span</span> <span class="token punctuation">{</span><br>    <span class="token property">font-size</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>What are the different ways Svelte could transform this component?</p>
<p>One option is to only apply the scoping class to the first selector in the rule, so the rules become <code>ul.svelte li</code> and <code>ul.svelte li span</code>. However, this could cause unwanted style leakage. If this component contained child components, elements in those components could match the rule.</p>
<p>Another option is to apply the scoping class to every selector in the rule, so the rules would become <code>ul.svelte li.svelte</code> and <code>ul.svelte li.svelte span.svelte</code>. This <em>would</em> prevent any styles from leaking to child components, but it does add the scoping class more times than is necessary. It would also unnecessarily increase specificity, which is a problem we'll return to later.</p>
<p>What Svelte actually does is somewhere in the middle: it applies the scoping class to the first and last selector of each rule. The styles are transformed to the following:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">ul.svelte-gxa857 li.svelte-gxa857</span> <span class="token punctuation">{</span><br>  <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><span class="token selector">ul.svelte-gxa857 li span.svelte-gxa857</span> <span class="token punctuation">{</span><br>  <span class="token property">font-size</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>This is the best of both worlds: styles don't leak out (because the rule must start and end inside the component) and we don't add more classes than necessary.</p>
<h2>Specificity and scoping</h2>
<p>Now if you think you have a handle on things, let's tweak our markup and styles a bit. What styles do you think Svelte generates in this case?</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ul</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Apples<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🍎<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Bananas<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🍌<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>name<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Carrots<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">></span></span>🥕<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ul</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">ul li span</span> <span class="token punctuation">{</span><br>    <span class="token property">font-size</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  <span class="token selector">.name</span> <span class="token punctuation">{</span><br>    <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>In this case, Svelte outputs the following CSS:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">ul.svelte-1pr62yn li span.svelte-1pr62yn</span> <span class="token punctuation">{</span><br>  <span class="token property">font-size</span><span class="token punctuation">:</span> 24px<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><span class="token selector">.name.svelte-1pr62yn.svelte-1pr62yn</span> <span class="token punctuation">{</span><br>  <span class="token property">font-size</span><span class="token punctuation">:</span> 18px<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Woah! Svelte transformed the 3-selector rule the same way, but added the hash class <em>twice</em> to the <code>.name</code> rule! Why would it do that?</p>
<p>This traces back to a concept called <em>CSS specificity</em>. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">Specificity</a> is how the browser determines what CSS rules should take precedence over others. In general, certain types of CSS selectors are more specific and thus have higher priority. For instance, a class selector (like <code>.list</code>) is more specific than an element selector (like <code>ul</code>). If both <code>.list</code> and <code>ul</code> define a value for font-size, the <code>.list</code> value will win since it's more specific.</p>
<p>Also, the amount of each type of selector matters. The more of a type of a selector in a given CSS rule, the more specific it is. So, a selector with two classes will be more specific than a selector with one class.</p>
<p>I'm drastically over-simplifying things (specificity can support an entire blog post in itself!), so check out <a href="https://web.dev/learn/css/specificity/">web.dev's Learn CSS module</a> for more details.</p>
<p>So, the reason Svelte adds two class selectors instead of one is to keep the specificity order intact. Before the scoping classes were added, the rules had the following specificity order (from highest to lowest):</p>
<ol>
<li><code>.name</code> (specificity 0-1-0)</li>
<li><code>ul li span</code> (specificity 0-0-3)</li>
</ol>
<p>But after the classes were added the specificity changed. Here's what the specificity would've been if Svelte <strong>didn't</strong> add the hash class twice:</p>
<ol>
<li><code>ul.svelte li span.svelte</code> (specificity 0-2-3)</li>
<li><code>.name.svelte</code> (specificity 0-2-0)</li>
</ol>
<p>(For how those specificity values were calculated, see the resources linked above or the <a href="https://specificity.keegan.st/">CSS Specificity Calculator</a>).</p>
<p>Because multi-selector rules have two classes added in the generated styles and single-selector rules only have one, the specificity order of the rules changed. This could mean that different styles take precedence than if Svelte <em>didn't</em> scope the styles. In our example, the name's font size would be 24px (as defined by <code>ul li span</code>) instead of 18px (as defined by <code>.name</code>)—the opposite of what you'd expect looking at the raw CSS.</p>
<p>Svelte prevents the specificity order from changing in an interesting way. It keeps track of how many classes are added to each CSS rule, and makes sure each rule has its specificity increased by the same amount. Since <code>.name</code> only had one scoping class applied, Svelte adds a second class to preserve the specificity order:</p>
<ol>
<li><code>.name.svelte.svelte</code> (specificity 0-3-0)</li>
<li><code>ul.svelte li span.svelte</code> (specificity 0-2-3)</li>
</ol>
<p>By making sure the specificity order remains the same, the scoped CSS produces the same result as the raw CSS.</p>
<p>If you're interested in seeing how this is implemented in the Svelte compiler, see <a href="https://github.com/sveltejs/svelte/pull/4146">Svelte PR #4146</a>.</p>
<h2>Specificity wars</h2>
<p>Because Svelte's scoping method increases the specificity of your CSS by adding classes, you may run into issues if you have global styles that you expect to be inherited. For instance, let's say you have the following <strong>global</strong> styles (e.g., in an external stylesheet):</p>
<pre class="language-css"><code class="language-css"><span class="token selector">a</span> <span class="token punctuation">{</span><br>  <span class="token property">color</span><span class="token punctuation">:</span> purple<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token selector">a:hover</span> <span class="token punctuation">{</span><br>  <span class="token property">color</span><span class="token punctuation">:</span> green<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>Then, in a Svelte component, you override the default link color:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://svelte.dev<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Ordinary link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>special-link<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://svelte.dev<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Exciting link<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.special-link</span> <span class="token punctuation">{</span><br>    <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span></code></pre>
<p>What color would you expect the link to be <em>on hover</em>?</p>
<p>If you were writing these styles without Svelte's scoping, the link would be red by default (as specified in the component) but green on hover (as specified in the global styles). This is because <code>a:hover</code> is more specific (0-1-1) than <code>.special-link</code> (0-1-0). However, because Svelte added a scoping class, we should really be comparing <code>a:hover</code> to <code>.special-link.svelte</code>, which has a specificity of 0-2-0. Because of this, the <code>.special-link</code> styles also apply when the link is hovered, which may be unexpected.</p>
<p>This problem is exacerbated when Svelte adds multiple scoping classes. If Svelte adds two classes to <code>.special-link</code>, the component styles will be more specific and even more likely to unintentionally override global styles. Unfortunately, there isn't an easy way to work around this behavior. If you want your global styles to apply in this situation, you'll need to find a way to increase their specificity (e.g. by adding <code>!important</code> or <a href="https://web.dev/learn/css/specificity/#pragmatically-increasing-specificity">doubling up on classes</a>).</p>
<p>There's currently an open <a href="https://github.com/sveltejs/svelte/issues/4374">Svelte issue</a> objecting to Svelte adding more than one scoping class, though it's not clear how to solve it without re-introducing the original <a href="https://github.com/sveltejs/svelte/issues/1277">issue around specificity order</a>. There isn't an obvious improvement to be made in the Svelte compiler either—Svelte needs to add <em>something</em> to the CSS rules to make sure they only apply to a single component, and that will increase the specificity. Perhaps <a href="https://drafts.csswg.org/css-scoping-2/#scoped-styles">native CSS scoping</a> will help, though the spec is still being drafted. Until then, the cost of Svelte's style scoping is some occasional specificity clashes.</p>
<p>EDIT: in fact, there is a way to scope styles without increasing specificity! I <a href="/posts/svelte-scoping-where/">wrote a post about it</a>.</p>
<h2>Wrapping up</h2>
<p>I hope this article helped you understand Svelte's CSS scoping behavior better. Understanding why the compiler makes the decisions it does can help you write better Svelte components and make debugging easier.</p>
<p>If you're interested in going deeper, consider reading the <a href="https://github.com/sveltejs/svelte/blob/4f9a260ab17a9d2a013a72a4bca3bf96947062c0/src/compiler/compile/css/Stylesheet.ts">Stylesheet implementation</a> in the Svelte source code—it's surprisingly readable.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Using Custom Elements in Svelte</title>
    <link href="https://css-tricks.com/using-custom-elements-in-svelte/"/>
    <updated>2021-06-22T00:00:00Z</updated>
    <id>https://css-tricks.com/using-custom-elements-in-svelte/</id>
    
    <summary>Posted on CSS-Tricks: What to look out for when using custom elements in a Svelte application.</summary>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Solving the Tower of Hanoi with recursive Svelte templates</title>
    <link href="https://geoffrich.net/posts/svelte-tower-of-hanoi/"/>
    <updated>2021-04-12T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-tower-of-hanoi/</id>
    
    <content type="html"><![CDATA[
      <p>The <a href="https://en.wikipedia.org/wiki/Tower_of_Hanoi">Tower of Hanoi</a> is a classic mathematical puzzle that is often used as an introduction to recursion. We can express a solution to this problem only using Svelte's template syntax.</p>
<h2>What is the Tower of Hanoi?</h2>
<p>The Tower of Hanoi asks you to move a stack of disks from one rod to another. The disks have different diameters and begin with the largest disk on the bottom and the smallest disk on top. There are three rules:</p>
<ol>
<li>You can only move one disk at a time.</li>
<li>Only the top disk on a stack can be moved.</li>
<li>You can't place a larger disk on top of a smaller disk.</li>
</ol>
<p>To make this possible, a third intermediate rod is available to place disks on.</p>
<p>You may have encountered this problem in computer science curriculum, where it is used to introduce <a href="https://en.wikipedia.org/wiki/Recursion_(computer_science)">recursion</a>, i.e. a function calling itself.</p>
<p>We can make our Svelte templates recursive using the <code>&lt;svelte:self&gt;</code> element.</p>
<h2>The <code>&lt;svelte:self&gt;</code> element</h2>
<p>You can include a Svelte component recursively using the <a href="https://svelte.dev/docs#svelte_self"><code>&lt;svelte:self&gt;</code> element</a>. A common use for this element is a comment thread, e.g. on the <a href="https://hn.svelte.dev/">Svelte Hacker News clone</a>.</p>
<p>Since using the element by itself without any conditions causes an infinite loop, the Svelte compiler requires you to place <code>&lt;svelte:self&gt;</code> inside an if or each block, or inside a slot passed to a component.</p>
<p>For example, this would not compile because there is no point where the component will stop rendering itself.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> count<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Count: <span class="token language-javascript"><span class="token punctuation">{</span>count<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">count=</span><span class="token language-javascript"><span class="token punctuation">{</span>count <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token punctuation">/></span></span></code></pre>
<p>Adding an if statement to the above example will stop the recursion once <code>count</code> gets to zero.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> count<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">}</span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Count: <span class="token language-javascript"><span class="token punctuation">{</span>count<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">count=</span><span class="token language-javascript"><span class="token punctuation">{</span>count <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">}</span></span><span class="token punctuation">/></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>You can check out the <a href="https://svelte.dev/tutorial/svelte-self">Svelte tutorial</a> for another example of svelte:self in action.</p>
<p>Even with the compiler safeguards, you still need to be careful with the svelte:self element. You can place it inside an if statement and still cause an infinite loop. For example, incrementing <code>count</code> in the above component will result in an infinite loop since count will never be less than zero. Svelte will compile this component without issue, but rendering it in the browser will result in a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Too_much_recursion">&quot;too much recursion&quot; error</a> logged to the console. Just because it compiles doesn't mean it's safe!</p>
<h2>Writing a solution</h2>
<p>With the <code>&lt;svelte:self&gt;</code> element added to our toolbelt, let's use it to solve the Tower of Hanoi.</p>
<p>A traditional recursive JavaScript implementation of the Tower of Hanoi looks like this:</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">tower</span><span class="token punctuation">(</span><span class="token parameter">disk<span class="token punctuation">,</span> source<span class="token punctuation">,</span> intermediate<span class="token punctuation">,</span> destination</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>disk <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">move disk </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>disk<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> from </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>source<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> to </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>destination<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><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    <span class="token function">tower</span><span class="token punctuation">(</span>disk <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> source<span class="token punctuation">,</span> destination<span class="token punctuation">,</span> intermediate<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">move disk </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>disk<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> from </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>source<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> to </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>destination<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><br>    <span class="token function">tower</span><span class="token punctuation">(</span>disk <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> intermediate<span class="token punctuation">,</span> source<span class="token punctuation">,</span> destination<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>To move 3 disks from Tower A to Tower C, with Tower B acting as an intermediate, you call it like so:</p>
<pre class="language-js"><code class="language-js"><span class="token function">tower</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'Tower A'</span><span class="token punctuation">,</span> <span class="token string">'Tower B'</span><span class="token punctuation">,</span> <span class="token string">'Tower C'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token comment">/*<br>logs the following:<br>move disk 1 from Tower A to Tower C<br>move disk 2 from Tower A to Tower B<br>move disk 1 from Tower C to Tower B<br>move disk 3 from Tower A to Tower C<br>move disk 1 from Tower B to Tower A<br>move disk 2 from Tower B to Tower C<br>move disk 1 from Tower A to Tower C<br>*/</span></code></pre>
<p>A full explanation of the algorithm is outside of the scope of this post. Check out this post on <a href="https://www.freecodecamp.org/news/analyzing-the-algorithm-to-solve-the-tower-of-hanoi-problem-686685f032e3/">Free Code Camp</a> for an in-depth explanation.</p>
<p>Instead of a function that recursively calls itself, we can write this as a Svelte component that recursively renders itself. Note that we are able to use svelte:self because it is inside an else block.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token comment">&lt;!-- Tower.svelte --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">export</span> <span class="token keyword">let</span> disk<span class="token punctuation">,</span> source<span class="token punctuation">,</span> intermediate<span class="token punctuation">,</span> destination<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> disk <span class="token operator">===</span> <span class="token number">1</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Move disk <span class="token language-javascript"><span class="token punctuation">{</span>disk<span class="token punctuation">}</span></span> from <span class="token language-javascript"><span class="token punctuation">{</span>source<span class="token punctuation">}</span></span> to <span class="token language-javascript"><span class="token punctuation">{</span>destination<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">:</span><span class="token keyword">else</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">disk=</span><span class="token language-javascript"><span class="token punctuation">{</span>disk <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">}</span></span> <span class="token attr-name">source=</span><span class="token language-javascript"><span class="token punctuation">{</span>source<span class="token punctuation">}</span></span> <span class="token attr-name">intermediate=</span><span class="token language-javascript"><span class="token punctuation">{</span>destination<span class="token punctuation">}</span></span> <span class="token attr-name">destination=</span><span class="token language-javascript"><span class="token punctuation">{</span>intermediate<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Move disk <span class="token language-javascript"><span class="token punctuation">{</span>disk<span class="token punctuation">}</span></span> from <span class="token language-javascript"><span class="token punctuation">{</span>source<span class="token punctuation">}</span></span> to <span class="token language-javascript"><span class="token punctuation">{</span>destination<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">svelte:</span>self</span> <span class="token attr-name">disk=</span><span class="token language-javascript"><span class="token punctuation">{</span>disk <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">}</span></span> <span class="token attr-name">source=</span><span class="token language-javascript"><span class="token punctuation">{</span>intermediate<span class="token punctuation">}</span></span> <span class="token attr-name">intermediate=</span><span class="token language-javascript"><span class="token punctuation">{</span>source<span class="token punctuation">}</span></span> <span class="token attr-name">destination=</span><span class="token language-javascript"><span class="token punctuation">{</span>destination<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><br><span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span></code></pre>
<p>Each line of the function directly translates to Svelte template syntax. <code>if</code> and <code>else</code> translate to if/else blocks, <code>tower()</code> becomes <code>&lt;svelte:self&gt;</code>, and instead of <code>console.log</code>, we render a list item.</p>
<p>Our component can be used like so:</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</span><span class="token punctuation">></span></span><br>	<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>Tower</span> <span class="token attr-name">disk</span><span class="token attr-value"><span class="token punctuation">=</span>3</span> <span class="token attr-name">source</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Tower A<span class="token punctuation">"</span></span> <span class="token attr-name">intermediate</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Tower B<span class="token punctuation">"</span></span> <span class="token attr-name">destination</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Tower C<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span><br><br><span class="token comment">&lt;!-- Renders<br>1. Move disk 1 from Tower A to Tower C<br>2. Move disk 2 from Tower A to Tower B<br>3. Move disk 1 from Tower C to Tower B<br>4. Move disk 3 from Tower A to Tower C<br>5. Move disk 1 from Tower B to Tower A<br>6. Move disk 2 from Tower B to Tower C<br>7. Move disk 1 from Tower A to Tower C<br>--></span></code></pre>
<p>You can see this component in action in the <a href="https://svelte.dev/repl/5fd3847e93d94ee2b3aee8258889b3fb?version=3.37.0">Svelte REPL</a>. The code is also available on <a href="https://github.com/geoffrich/svelte-hanoi">GitHub</a>.</p>
<p>While this is not the most efficient way to solve the puzzle, it shows the power of Svelte's template syntax.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>4 takeaways from axe-con 2021</title>
    <link href="https://geoffrich.net/posts/axecon-2021/"/>
    <updated>2021-03-29T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/axecon-2021/</id>
    
    <content type="html"><![CDATA[
      <p>I recently had the pleasure of attending Deque's <a href="https://www.deque.com/axe-con/">axe-con</a> digital accessibility conference. Over the course of two days, I attended multiple sessions about many different facets of accessibility. I mainly focused on the developer track at the conference, though I sampled a few talks from other tracks. Here are some of the highlights and what I took away from the sessions I attended.</p>
<p>I link the associated talk in each section, though you may need to <a href="https://www.deque.com/axe-con/register/">register</a> for the conference to view the recording. Registration is still open at time of publishing.</p>
<h2>Assistive tech is more than just screenreaders</h2>
<blockquote>
<p>This is indeed a very good solution for a screen reader user navigating using form controls. But you do not want to do this, because even though it improves the experience for some screen reader users, it excludes users of other assistive technologies and makes these buttons inaccessible to them, and a pain to use. More specifically, inserting visually hidden text in the middle of a visible string of text, or a visible label on a button prevents the users browsing and navigating using voice commands from interacting with the button.</p>
<p>—Sara Soueidan, &quot;Applied Accessibility: Practical Tips for Building More Accessible Front-Ends&quot;</p>
</blockquote>
<p>When I think of types of assistive technology, I immediately think of screen readers. However, there are many other <a href="https://www.w3.org/WAI/people-use-web/tools-techniques/">tools</a> that people with disabilities use to access websites. One of those tools is speech input software such as Dragon Naturally Speaking, which allows people to control their web browser using their voice.</p>
<p>Sara Soueidan's talk <a href="https://www.axe-con.com/event/applied-accessibility-practical-tips-for-building-more-accessible-front-ends/">Applied Accessibility</a> gave an example of where improving the experience for screen reader users creates a worse experience for speech input users. When you have multiple &quot;Add to cart&quot; buttons on a page, you may consider adding visually hidden text to each button indicating the associated product (e.g. Add [book] to cart). This makes it clear to screen reader software which product will be added to the cart.</p>
<p>However, putting the hidden text in the middle of the label creates an issue for speech input users. When they instruct the software to click the &quot;Add to cart&quot; button, the software is unable to find it since the actual button name is &quot;Add book to cart.&quot; If we instead add the hidden text at the end of the label (e.g. Add to cart[, book]), the speech input software will be able to find the button. For a more in-depth explanation, read <a href="https://www.sarasoueidan.com/blog/accessible-text-labels/">Accessible Text Labels For All</a> on Sara's blog.</p>
<p>Accessibility is complex and it is not enough to consider one type of person or device. When making improvements, you need to be careful that you're not improving the experience for one but making it worse for another. In the future, I will make sure to consider speech input users in my work.</p>
<h2>Accessibility testing requires multiple levels</h2>
<p>Mark Steadman's talk <a href="https://www.axe-con.com/event/automated-accessibility-testing-in-javascript-frameworks/">Automated Accessibility Testing in JavaScript Frameworks</a> showed how to automate accessibility testing using <a href="https://github.com/dequelabs/axe-core">axe-core</a>. He emphasized the importance of testing accessibility at multiple levels of test, instead of relying solely on unit, integration, or manual tests.</p>
<p>There are many accessibility issues that can be caught at the component level, such as missing alt text and invalid ARIA attributes. However, some issues are only detectable when components interact with each other on an actual page. This includes issues like <a href="https://dequeuniversity.com/rules/axe/3.5/duplicate-id">duplicate IDs</a>, <a href="https://dequeuniversity.com/rules/axe/4.1/identical-links-same-purpose">links with the same name</a> but different purposes, and <a href="https://dequeuniversity.com/rules/axe/3.5/heading-order">missing heading levels</a>.</p>
<p>We need both kinds of tests (as well as manual testing) to maximize the value of our automated tests.</p>
<h2>Reduced motion does not mean no motion</h2>
<p>Val Head's talk <a href="https://www.axe-con.com/event/making-motion-inclusive/">Making Motion Inclusive</a> showed how to design and use interface animation responsibly. Being respectful towards those with motion sensitivities does not mean disabling animation entirely—animation has UX benefits and can reduce cognitive load. Instead, we should identify potentially triggering animation and see if we can replace it with something else, like an opacity transition. If your site heavily relies on motion, consider a dedicated toggle like the <a href="https://animal-crossing.com/">Animal Crossing</a> site has.</p>
<p>Some motion animation can be more triggering than others. In particular, we need to be careful with spinning and parallax effects. You can find an in-depth look at what animation could be potentially triggering in the speaker's article on <a href="https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/">A List Apart</a>.</p>
<h2>Read the ARIA docs</h2>
<blockquote>
<p>Normally visiting the official standard or spec for any kind of web technology is reserved for the extremely academic types. Most of you have probably never read the official ECMAScript standard, but I want you to treat the official ARIA standard as an API guide to get the most up-to-date information on available properties and options. If you wanna know what something means I urge you to come here first before searching online or reading on some blog.</p>
<p>—Gerard Cohen, &quot;ARIA Spec for the Uninitiated&quot;</p>
</blockquote>
<p>Gerard Cohen's talk <a href="https://www.axe-con.com/event/aria-spec-for-the-uninitiated/">ARIA Spec for the Uninitiated</a> gave an overview of the ARIA spec and how to apply it to the components you build. Unlike other web specs like the ECMAScript Language Specification, the ARIA spec is surprisingly approachable. It's written like an API guide and should be referenced when you want to know how to use an ARIA role or attribute.</p>
<p>With that said, he made sure to emphasize the <a href="https://www.w3.org/TR/using-aria/#firstrule">first rule of ARIA</a>—ARIA should only be used as a last resort, and you should use HTML if possible. Since support for ARIA varies across different browsers and assistive technology, you have to know what you're doing and be willing to manually test.</p>
<p>The most valuable part of his presentation for me was the context he gave around the ARIA authoring practices. When I first encountered these, I treated them as the standard way to implement complex UI patterns accessibly. However, they are only suggestions and following them to the letter does not mean your component is automatically accessible. They don't account for varying ARIA support between browsers, don't take mobile or touch into consideration, and may overuse ARIA, since they're intended as a tool to test ARIA implementations. You need to test the patterns yourself to make sure they work as expected.</p>
<p>The <a href="https://github.com/w3c/aria-practices/issues">ARIA practices GitHub</a> is a good resource to see where certain patterns fall short.</p>
<h2>Wrapping up</h2>
<p>I really enjoyed the talks I attended, and there's still plenty I want to catch up on! If you want to see what others thought, check out the <a href="https://twitter.com/search?q=%23axecon">#axecon</a> tag on Twitter or <a href="https://benmyers.dev/blog/axecon-2021/">Ben Myers' write-up</a> on the talks he attended.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Accessible Svelte transitions</title>
    <link href="https://geoffrich.net/posts/accessible-svelte-transitions/"/>
    <updated>2021-03-15T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/accessible-svelte-transitions/</id>
    
    <content type="html"><![CDATA[
      <p><strong>Update 11/2021:</strong> this post was adapted into a lightning talk I recorded for the <a href="/posts/svelte-summit-2021/">Fall 2021 Svelte Summit.</a></p>
<p>Svelte's built-in <a href="https://svelte.dev/tutorial/transition">transition</a> functionality makes it easy to animate elements as they are added to and removed from the DOM. It's as simple as adding a <code>transition:</code> directive to an element and passing one of the built-in transition functions.</p>
<p>However, we need to be mindful of accessibility issues around animation. Some transitions could trigger motion sickness for those with motion sensitivities. I will go over which Svelte transitions could cause accessibility issues and how to remove or replace them based on the user's preference.</p>
<h2>What kind of animations cause motion sickness?</h2>
<p>Making our sites accessible does not mean removing animation entirely. When used tastefully, animation can make web sites more intuitive to use. We mainly need to be careful with animation that involves a large amount of movement. Animation that does not involve movement (e.g. color or opacity animation) is less likely to pose a problem to those sensitive to motion. Val Head has a great article on <a href="https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/">A List Apart</a> that covers this subject in depth.</p>
<p>Of Svelte's seven built-in transition functions, five of them involve motion and could pose a problem: fly, slide, scale, draw, and crossfade. The other two, fade and blur, do not involve motion and shouldn't cause any issues.</p>
<p>There are two options to apply the user's motion preference to Svelte's transitions: one in CSS, and one in JS. In both cases, we will use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduced-motion</a> media query to detect if the user has requested reduced motion.</p>
<h2>Option 1: Globally disable all animation</h2>
<p>Since Svelte's built-in transitions are applied in CSS, we can disable them in CSS. The prefers-reduced-motion media query will detect if the user has requested reduced motion in their device settings. You can add the following to your global styles to disable all CSS animation.</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br>  <span class="token selector">*</span> <span class="token punctuation">{</span><br>    <span class="token property">animation-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">animation-iteration-count</span><span class="token punctuation">:</span> 1 <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>    <span class="token property">animation-delay</span><span class="token punctuation">:</span> 0.01ms <span class="token important">!important</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span></code></pre>
<p>Because Svelte's transitions are applied using inline styles, we need <strong>!important</strong> here to win the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">specificity</a> battle.</p>
<p>After applying this to your global styles, Svelte's built in transition functions will no longer have any effect when reduced motion is enabled. This is the safest option, since you can be sure that Svelte's built-in transitions won't trigger motion sickness if the user has enabled the setting. However, it has some downsides.</p>
<ol>
<li>It has no effect on any <a href="https://svelte.dev/tutorial/custom-js-transitions">custom JS transitions</a> (written using <code>tick</code>)</li>
<li>It also disables safe animations that do not involve motion, like fade.</li>
<li>Because it's global and uses !important, it's hard to undo if you want to add animation back for some elements.</li>
</ol>
<h2>Option 2: Reactively swap out transitions</h2>
<p>We can have more fine-grained control by replacing problematic transitions with something else when reduced motion is requested. For example, instead of having something fly in, we could fade it in instead. This is how iOS implements reduced motion transitions. When reduced motion is turned on, apps fade into view instead of the traditional zoom. This preserves the polish that animations add to a UI while also avoiding animations that could trigger motion sickness.</p>
<p>We'll use the <code>reducedMotion</code> store from my <a href="/posts/svelte-prefers-reduced-motion-store/">previous article</a> to detect if the user has requested reduced motion. Using a Svelte store will make it easy to react to user preference changes.</p>
<p>We can define the transition to use for an element in a <a href="https://svelte.dev/tutorial/reactive-declarations">reactive declaration</a>. When the value of the store changes, <code>rmTransition</code> automatically updates.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>reducedMotion<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./reducedMotion'</span><span class="token punctuation">;</span><br>  <span class="token keyword">import</span> <span class="token punctuation">{</span>fly<span class="token punctuation">,</span> fade<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> showCards <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br><br>  <span class="token literal-property property">$</span><span class="token operator">:</span> rmTransition <span class="token operator">=</span> $reducedMotion <span class="token operator">?</span> fade <span class="token operator">:</span> fly<span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre>
<p>You can use <code>rmTransition</code> just like any other Svelte transition function.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">transition:</span>rmTransition=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">300</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span></code></pre>
<p>When reduced motion is enabled, the element will fade in. When reduced motion is not enabled, it will fly in. See it in action in this <a href="https://svelte.dev/repl/470f23fcce014693be8333016059c223?version=3.35.0">REPL</a>.</p>
<p>Note that the transitions will share the same set of parameters. It probably won't hurt anything, since fade will ignore parameters that it doesn't understand (e.g. x and y). However, if you wanted to make changes to the parameters when prefers-reduced-motion is enabled, you can define a custom transition with the desired parameters hard-coded.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">customFade</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> params</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">fade</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">duration</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 punctuation">;</span><br><span class="token literal-property property">$</span><span class="token operator">:</span> cardTransition <span class="token operator">=</span> $reducedMotion <span class="token operator">?</span> customFade <span class="token operator">:</span> fly<span class="token punctuation">;</span></code></pre>
<p>If you often find yourself making the same replacement, we can move this logic into a reactive store that can be used in any component.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span>derived<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/store'</span><span class="token punctuation">;</span><br><span class="token keyword">import</span> <span class="token punctuation">{</span>fly<span class="token punctuation">,</span> fade<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'svelte/transition'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> accessibleFly <span class="token operator">=</span> <span class="token function">derived</span><span class="token punctuation">(</span>reducedMotion<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">$reducedMotion<span class="token punctuation">,</span> <span class="token keyword">set</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>$reducedMotion<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token function">set</span><span class="token punctuation">(</span>fade<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    <span class="token function">set</span><span class="token punctuation">(</span>fly<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This store is derived from our <code>reducedMotion</code> store. When the value of <code>reducedMotion</code> changes, this store will automatically replace fly with fade. We can use the value of this store as a replacement for Svelte's built-in fly transition.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name"><span class="token namespace">transition:</span>$accessibleFly=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">300</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span></code></pre>
<h2>Wrapping up</h2>
<p>You have two options to respect the user's motion preference when using Svelte transitions. You can disable all animations globally in CSS, which is the safest option but also disables animations that do not trigger motion sickness. Alternatively, you can swap out problematic transitions with a safer option when the user requests it, but this requires vigilance whenever implementing a new transition.</p>
<p>We all should do our part to make the web a more accessible place. If you want to learn more about motion sensitivities and the web, I've linked some articles below that I found helpful while working on this piece.</p>
<ul>
<li>&quot;Designing With Reduced Motion For Motion Sensitivities&quot; by Val Head, <a href="https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/">Smashing Magazine</a></li>
<li>&quot;Designing Safer Web Animation For Motion Sensitivity&quot; by Val Head, <a href="https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/">A List Apart</a></li>
<li>&quot;Accessibility for Vestibular Disorders: How My Temporary Disability Changed My Perspective&quot; by Facundo Corradini, <a href="https://alistapart.com/article/accessibility-for-vestibular/">A List Apart</a></li>
<li>&quot;Revisiting prefers-reduced-motion, the reduced motion media query&quot; by Eric Bailey, <a href="https://css-tricks.com/revisiting-prefers-reduced-motion-the-reduced-motion-media-query/">CSS Tricks</a></li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>A Svelte store for prefers-reduced-motion</title>
    <link href="https://geoffrich.net/posts/svelte-prefers-reduced-motion-store/"/>
    <updated>2021-03-01T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-prefers-reduced-motion-store/</id>
    
    <content type="html"><![CDATA[
      <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion">prefers-reduced-motion</a> media query is used to detect if the user has requested that animation and motion be minimized. Website animation may trigger motion sickness for those with vestibular disorders, and it is important to disable non-essential animations for these users. prefers-reduced-motion is often used in a CSS stylesheet to disable certain animations, though it can also be used to modify animations applied with JavaScript.</p>
<p>In this post, I will show you how to make a custom Svelte store whose value will indicate whether the user has requested reduced motion. The store's value will automatically update if the user's preference changes. In an upcoming post, I will show how you can apply this store to Svelte's transition and motion packages. This article will focus on the Svelte side of things — check out <a href="https://css-tricks.com/introduction-reduced-motion-media-query/">CSS Tricks</a> and <a href="https://web.dev/prefers-reduced-motion/">web.dev</a> for more on prefers-reduced-motion itself.</p>
<h2>Detecting prefers-reduced-motion</h2>
<p>Here's how you will often see prefers-reduced-motion used in CSS.</p>
<pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@media</span> <span class="token punctuation">(</span><span class="token property">prefers-reduced-motion</span><span class="token punctuation">:</span> reduce<span class="token punctuation">)</span></span> <span class="token punctuation">{</span><br>  <span class="token comment">/* <br>    Anything inside this block will apply when the user has <br>    requested reduced motion <br>  */</span><br><span class="token punctuation">}</span></code></pre>
<p>In JavaScript, you can detect the same preference using <code>window.matchMedia</code>.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">prefersReducedMotion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> mediaQueryList <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span><span class="token string">'(prefers-reduced-motion: reduce)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> mediaQueryList<span class="token punctuation">.</span>matches<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>If you want to react when the user changes their preference, you can attach an event listener to the media query list.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> mediaQueryList <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span><span class="token string">'(prefers-reduced-motion: reduce)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>mediaQueryList<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> handlePreferenceChange<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">function</span> <span class="token function">handlePreferenceChange</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><br>    <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">prefers-reduced-motion: reduce is </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>event<span class="token punctuation">.</span>matches <span class="token operator">?</span> <span class="token string">'enabled'</span> <span class="token operator">:</span> <span class="token string">'disabled'</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><br>  <span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>If you run the above code into your browser console and update your motion preferences, you'll see a message logged to the console informing you of the new preference. Here's how to simulate the setting in <a href="https://developers.google.com/web/updates/2019/10/devtools#userpreferences">Chrome DevTools</a> and where to enable the setting in <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion#user_preferences">various OSes and Firefox</a>.</p>
<h2>Using a Svelte store</h2>
<p>We can wrap this code in a reusable Svelte store so that we access motion preference anywhere in our app using Svelte's reactive <code>$store</code> syntax. The store's value will be true if the user has requested reduced motion and will update in real time if the user changes their motion preference. If you are unfamiliar with Svelte's stores, I recommend checking out the <a href="https://svelte.dev/tutorial/writable-stores">official tutorial</a>.</p>
<p>Here's how we'll use the final product in a Svelte component.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>	<span class="token keyword">import</span> <span class="token punctuation">{</span> reducedMotion <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./reducedMotion'</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>reduced motion: <span class="token language-javascript"><span class="token punctuation">{</span>$reducedMotion<span class="token punctuation">}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>First, we initialize a <a href="https://svelte.dev/tutorial/readable-stores">readable store</a> from Svelte's built-in store library. We detect whether reduced motion is enabled using <code>window.matchMedia</code> and pass it to <code>readable</code> to set the store's initial value.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> reducedMotionQuery <span class="token operator">=</span> <span class="token string">'(prefers-reduced-motion: reduce)'</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">getInitialMotionPreference</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span>reducedMotionQuery<span class="token punctuation">)</span><span class="token punctuation">.</span>matches<span class="token punctuation">;</span><br><br><span class="token keyword">export</span> <span class="token keyword">const</span> reducedMotion <span class="token operator">=</span> <span class="token function">readable</span><span class="token punctuation">(</span><span class="token function">getInitialMotionPreference</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><code>readable</code> also takes an optional second argument — a callback that will run the first time someone subscribes to the store. This is a good place to set up event listeners. A set function is passed to the callback so we can update the store when events are triggered.</p>
<p>We'll add a change event listener to the media query list so that we can update the store if the user's preference changes. When the store is updated, anyone subscribing to this store will be notified of the new value.</p>
<p>Since we're adding an event listener, we need to remove it when it's no longer needed. We can return a function from the store callback that will be run when the last consumer unsubscribes from the store and remove the event listener there.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">const</span> reducedMotion <span class="token operator">=</span> <span class="token function">readable</span><span class="token punctuation">(</span><span class="token function">getInitialMotionPreference</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token parameter"><span class="token keyword">set</span></span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> <span class="token function-variable function">updateMotionPreference</span> <span class="token operator">=</span> <span class="token parameter">event</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    <span class="token function">set</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>matches<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">const</span> mediaQueryList <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">matchMedia</span><span class="token punctuation">(</span>reducedMotionQuery<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  mediaQueryList<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> updateMotionPreference<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>    mediaQueryList<span class="token punctuation">.</span><span class="token function">removeEventListener</span><span class="token punctuation">(</span><span class="token string">'change'</span><span class="token punctuation">,</span> updateMotionPreference<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>That's all there is to it! We can import this store anywhere in our application and get a reactive value based on the user's motion preferences. Check out this <a href="https://svelte.dev/repl/e9b0322383bd4922bed92056c106c643?version=3.34.0">Svelte REPL</a> to see our new store in action.</p>
<p>Come back next week to see how we can apply this store to Svelte's built-in transition and motion packages. Follow me on <a href="https://twitter.com/geoffrich_">Twitter</a> or <a href="https://dev.to/geoffrich/">DEV</a> to be notified when I publish the next article.</p>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Detecting sticky positioning with Svelte actions</title>
    <link href="https://geoffrich.net/posts/svelte-action-sticky/"/>
    <updated>2020-12-02T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/svelte-action-sticky/</id>
    
    <content type="html"><![CDATA[
      <p><code>position: sticky</code> is a CSS property that lets you &quot;stick&quot; an element to the top of the screen when it would normally be scrolled away. However, there is no native way to change the element's styling when it becomes stuck. In this article, I will show you how to detect and style a &quot;stuck&quot; element using an underused feature of the Svelte API: actions.</p>
<p>If you want to see the end result and don't want to read the explanation, here's my <a href="https://svelte.dev/repl/4ad71e00c86c47d29806e17f09ff0869?version=3">finished demo</a> in the Svelte REPL. You can also find the code on my <a href="https://github.com/geoffrich/svelte-sticky-demo">GitHub</a>.</p>
<h2>What is position: sticky?</h2>
<p>In essence, sticky positioning lets you stick an element on screen once a certain point in the page is crossed. Per <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#Sticky_positioning">MDN</a>:</p>
<blockquote>
<p>Sticky positioning can be thought of as a hybrid of relative and fixed positioning. A stickily positioned element is treated as relatively positioned until it crosses a specified threshold, at which point it is treated as fixed until it reaches the boundary of its parent.</p>
</blockquote>
<p>A common use case is to keep some information in view that would normally be scrolled off screen. For instance, if someone is changing their flight online, you may want to stick their current flight information to the top of the screen as they scroll through other flight options. Here are some other examples of <a href="https://mastery.games/post/position-sticky/">position: sticky in practice</a>.</p>
<p>Sticky positioning is supported in the vast majority of browsers (<a href="https://caniuse.com/?search=position%20sticky">Can I Use</a>). Even if a browser doesn't support it, it can be treated as a progressive enhancement and gracefully fall back to static positioning.</p>
<h2>How do I change the appearance of an element when it becomes stuck?</h2>
<p>You can't, at least not natively, and this is intentional. If you had a <code>:stuck</code> selector, you could easily write a rule that would result in an infinite loop. For instance, look at the following:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">:stuck</span> <span class="token punctuation">{</span><br>  <span class="token property">position</span><span class="token punctuation">:</span> static<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>With this rule, the element becoming stuck would trigger static positioning, which would make it unstuck, so the rule would no longer apply, and so on until the end of time.</p>
<p>You can find a more detailed discussion of the issue on the <a href="https://wiki.csswg.org/faq#selectors-that-depend-on-layout">CSS Working Group wiki</a>. If you want to change styling when an element becomes stuck, you're going to have to implement it yourself with JavaScript. You should still be careful, as you can run into similar infinite loop issues on your own. Getting this wrong could result in a jittering screen that is very unpleasant for the user.</p>
<p>I will show you how to detect a &quot;stuck&quot; element using Svelte actions, though it could easily be written without a framework as well. If you are using Svelte, writing it as an action will allow this functionality to be re-used in any component you want with minimal boilerplate.</p>
<h2>What is a Svelte action?</h2>
<p>A Svelte action is a function that runs when a node is rendered into the DOM. They're commonly used for adding custom event handling logic or interfacing with external libraries, but the sky's the limit! You can do anything you want to the node inside that function. I recommend looking at the official <a href="https://svelte.dev/tutorial/actions">Svelte tutorial</a> on them to learn more. I've linked some other resources at the end of the article.</p>
<p>Writing our sticky functionality as an action lets us put all the imperative DOM code in one place that can be reused by multiple components. Here's what an action looks like in practice. All you need is a function that takes two arguments: a node and an (optional object of parameters).</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// sticky.js</span><br><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">sticky</span><span class="token punctuation">(</span><span class="token parameter">node<span class="token punctuation">,</span> <span class="token punctuation">{</span>stickToTop<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token comment">// do stuff</span><br><span class="token punctuation">}</span></code></pre>
<p>We pass the parameter <code>stickToTop</code> into the action to indicate whether the node will be stuck to the top or bottom. We'll go into how this will be used later.</p>
<p>One you have your action, you can attach it to a node with <code>use</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> sticky <span class="token keyword">from</span> <span class="token string">"./sticky"</span><span class="token punctuation">;</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><br>  <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sticky<span class="token punctuation">"</span></span><br>  <span class="token attr-name"><span class="token namespace">use:</span>sticky=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">stickToTop</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>  I use position: sticky!<br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span></code></pre>
<p>When the <code>h2</code> appears in the DOM, the <code>sticky</code> function will run and we'll be off to the races!</p>
<h2>Detecting stickiness</h2>
<p>The way we'll detecting our node becoming stuck is with two &quot;sentinel&quot; divs: one at the top of the node's parent and one at the bottom. If the top sentinel exits the viewport, then a top position: sticky element is currently stuck. If the bottom sentinel exits the viewport, then a bottom position: sticky element is currently stuck.</p>
<p>Here's a gif of the sentinel in action. For the purposes of this demo, I've given the sentinel a height and colored it blue. See how the heading style changes once the sentinel travels off-screen.</p>
<p><img src="/images/svelte-action-sticky/sentinel.gif" alt="Demonstration of applied sticky styling when the top sentinel travels off-screen"></p>
<p>To accomplish this, let's first create and insert our sentinel divs inside our <code>sticky</code> function.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> sentinelStyle <span class="token operator">=</span> <span class="token string">'position: absolute; height: 1px;'</span><span class="token punctuation">;</span><br><span class="token keyword">const</span> stickySentinelTop <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>stickySentinelTop<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'stickySentinelTop'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>stickySentinelTop<span class="token punctuation">.</span>style <span class="token operator">=</span> sentinelStyle<span class="token punctuation">;</span><br>node<span class="token punctuation">.</span>parentNode<span class="token punctuation">.</span><span class="token function">prepend</span><span class="token punctuation">(</span>stickySentinelTop<span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> stickySentinelBottom <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'div'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>stickySentinelBottom<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'stickySentinelBottom'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>stickySentinelBottom<span class="token punctuation">.</span>style <span class="token operator">=</span> sentinelStyle<span class="token punctuation">;</span><br>node<span class="token punctuation">.</span>parentNode<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>stickySentinelBottom<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The classes aren't strictly necessary, but they make it clear why the divs are there if you saw them in the dev tools inspector. We also give the sentinels a height — for whatever reason, the demo was not working properly in Safari if I did not set a height. We set <code>position: absolute</code> so that the sentinels do not take up space in the document.</p>
<p>We then initialize an intersection observer to observe either the top or bottom sentinel, depending on the <code>stickToTop</code> parameter passed to the action. The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer API</a> allows us to execute a function when a certain node exits or enters the viewport. If the observer fires and the sentinel is outside of the viewport (i.e., not intersecting), then the element must be stuck (except for an edge case we'll cover later). If the sentinel is within the viewport, then the sticky element cannot be stuck.</p>
<p>Either way, we dispatch a custom <code>stuck</code> event with a property that indicates whether the element is sticking. The component using the action can listen to this event and update its state accordingly.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">intersectionCallback</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">entries</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token comment">// only observing one item at a time</span><br>  <span class="token keyword">const</span> entry <span class="token operator">=</span> entries<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> isStuck <span class="token operator">=</span> <span class="token operator">!</span>entry<span class="token punctuation">.</span>isIntersecting<span class="token punctuation">;</span><br>  node<span class="token punctuation">.</span><span class="token function">dispatchEvent</span><span class="token punctuation">(</span><br>    <span class="token keyword">new</span> <span class="token class-name">CustomEvent</span><span class="token punctuation">(</span><span class="token string">'stuck'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">detail</span><span class="token operator">:</span> <span class="token punctuation">{</span>isStuck<span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><br>  <span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> intersectionObserver <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">IntersectionObserver</span><span class="token punctuation">(</span>intersectionCallback<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">if</span> <span class="token punctuation">(</span>stickToTop<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  intersectionObserver<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>stickySentinelTop<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>  intersectionObserver<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>stickySentinelBottom<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>This is our basic implementation. It has some bugs, but it works well enough to start using it. We'll circle back to some edge cases and enhancements later in the post, but let's see how we can use this action in a Svelte component.</p>
<h2>Using the action in a Svelte component</h2>
<p>First, let's see how far we can get with just CSS and HTML.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.sticky</span> <span class="token punctuation">{</span><br>    <span class="token property">position</span><span class="token punctuation">:</span> sticky<span class="token punctuation">;</span><br>    <span class="token property">top</span><span class="token punctuation">:</span> 1rem<span class="token punctuation">;</span><br>    <span class="token property">background</span><span class="token punctuation">:</span> mistyrose<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sticky<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>    I use position: sticky!<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br>  <span class="token comment">&lt;!-- Lorem ipsum text truncated for readability --></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Lorem ipsum dolor sit amet...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Phasellus lobortis molestie turpis...<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span></code></pre>
<p>Presto! Render that HTML and you'll see a sticky header that stays visible when we scroll. My REPL has some extra styling, but this has the really essential stuff. No JavaScript is required for basic sticky positioning. It's only when you want to style it differently that you need a little something extra.</p>
<div class="callout">
<p>Note: <code>h2</code> is not the correct heading level to use if this is the only thing on your page. In my demo, this is being placed in a larger page that contains an <code>h1</code>. You should always <a href="https://dequeuniversity.com/rules/axe/4.0/heading-order?application=AxeChrome">ensure that headings are in a logical order</a> to aid screen reader navigation.</p>
</div>
<p>If you want to change something about the element or component when it's sticking to the top of the screen, you need to write some JavaScript. Let's add a script tag and update our markup to <code>use</code> the action we created.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token keyword">import</span> sticky <span class="token keyword">from</span> <span class="token string">'./sticky.js'</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">let</span> isStuck <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br><br>  <span class="token keyword">function</span> <span class="token function">handleStuck</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    isStuck <span class="token operator">=</span> e<span class="token punctuation">.</span>detail<span class="token punctuation">.</span>isStuck<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token comment">/* No change */</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><br>    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sticky<span class="token punctuation">"</span></span><br>    <span class="token attr-name"><span class="token namespace">use:</span>sticky=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">stickToTop</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>    <span class="token attr-name"><span class="token namespace">on:</span>stuck=</span><span class="token language-javascript"><span class="token punctuation">{</span>handleStuck<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>    I use position: sticky! (currently<br>    <span class="token language-javascript"><span class="token punctuation">{</span>isStuck <span class="token operator">?</span> <span class="token string">'sticking'</span> <span class="token operator">:</span> <span class="token string">'not sticking'</span><span class="token punctuation">}</span></span>)<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br>  <span class="token comment">&lt;!-- Lorem ipsum text truncated for readability --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span></code></pre>
<p>There's a bit more going on here, so let's break it down.</p>
<p>Our script tag is pretty slim — we import our sticky action and define a state variable <code>isStuck</code> and a function <code>handleStuck</code> to update that variable when the event is fired.</p>
<p>In our markup, we use the action we created earlier with <code>use:sticky</code> and pass in the action parameters. We also set up an event listener to listen for our custom <code>stuck</code> event. When the <code>h2</code> is added to the DOM, the action will initialize the observers with the callback we provided. Executing the callback will dispatch the <code>stuck</code> event and we can dynamically show whether the element is sticking or not. Pretty neat!</p>
<p>We can also update the styling of the element using our <code>isStuck</code> state variable.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"><br>  <span class="token comment">// No change</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css"><br>  <span class="token selector">.sticky</span> <span class="token punctuation">{</span> <span class="token comment">/* No change */</span> <span class="token punctuation">}</span><br><br>  <span class="token selector">.sticky.isStuck</span> <span class="token punctuation">{</span><br>    <span class="token property">background</span><span class="token punctuation">:</span> mintcream<span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><br><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><br>    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sticky<span class="token punctuation">"</span></span><br>    <span class="token attr-name"><span class="token namespace">class:</span>isStuck</span><br>    <span class="token attr-name"><span class="token namespace">use:</span>sticky=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">stickToTop</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>    <span class="token attr-name"><span class="token namespace">on:</span>stuck=</span><span class="token language-javascript"><span class="token punctuation">{</span>handleStuck<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>    I use position: sticky! (currently<br>    <span class="token language-javascript"><span class="token punctuation">{</span>isStuck <span class="token operator">?</span> <span class="token string">'sticking'</span> <span class="token operator">:</span> <span class="token string">'not sticking'</span><span class="token punctuation">}</span></span>)<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br>  <span class="token comment">&lt;!-- Lorem ipsum text truncated for readability --></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span></code></pre>
<p>Since we can't reference JavaScript variables in our styles directly, we need to add a class to the element so we have something to target in our CSS. We add the isStuck class using the Svelte <code>class:</code> directive. Now when the element is stuck, the color changes to mintcream 🍦.</p>
<p>Looks great! Unfortunately, we have a bug when we have multiple sticky elements on the page. Depending on your CSS, when scrolling down you may see a brief flash of the &quot;stuck&quot; styles on the heading coming into view. I changed the sticky colors to black and white and increased the transition duration to make the problem clear. See the gif below.</p>
<p><img src="/images/svelte-action-sticky/sticky-css-bug.gif" alt="Sticky styles briefly applied when scrolling down"></p>
<p>What's happening here? In our sticky action, we set <code>isStuck</code> based on the visibility of the top sentinel. When the page loads, the sentinel for the second heading is out of view, so the second heading applies the stuck styles. When we scroll down, the sentinel comes into view and the stuck styles are removed, resulting in a flash of the stuck styles as the styles transition.</p>
<p>To fix this, we need to check the Y position before dispatching the event. If the sentinel is coming into view from the bottom of the screen but we are observing an element sticking to the top, <code>isStuck</code> should be false. Similarly, if the sentinel is coming into view from the top of the screen but we are observing an element sticking to the bottom, <code>isStuck</code> should also be false. Here's what that looks like in code.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">intersectionCallback</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">entries</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> entry <span class="token operator">=</span> entries<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><br>  <span class="token keyword">let</span> isStuck <span class="token operator">=</span> <span class="token operator">!</span>entry<span class="token punctuation">.</span>isIntersecting <span class="token operator">&amp;&amp;</span> <span class="token function">isValidYPosition</span><span class="token punctuation">(</span>entry<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  node<span class="token punctuation">.</span><span class="token function">dispatchEvent</span><span class="token punctuation">(</span><br>    <span class="token keyword">new</span> <span class="token class-name">CustomEvent</span><span class="token punctuation">(</span><span class="token string">'stuck'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><br>      <span class="token literal-property property">detail</span><span class="token operator">:</span> <span class="token punctuation">{</span>isStuck<span class="token punctuation">}</span><br>    <span class="token punctuation">}</span><span class="token punctuation">)</span><br>  <span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span><br><br><span class="token keyword">const</span> <span class="token function-variable function">isValidYPosition</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>target<span class="token punctuation">,</span> boundingClientRect<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>target <span class="token operator">===</span> stickySentinelTop<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> boundingClientRect<span class="token punctuation">.</span>y <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    <span class="token keyword">return</span> boundingClientRect<span class="token punctuation">.</span>y <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>With that change, sticky styling is applied correctly.</p>
<h2>Another edge case: mutations</h2>
<p>I encountered another edge case while preparing the demo for this post — what happens if the content inside the component moves around? It's important that our sentinel nodes are at the top and bottom of the node's parent, but that is not guaranteed if Svelte dynamically inserts elements after the action has run.</p>
<p>For instance, let's say you had some content controlled by a checkbox that toggles <code>flag</code>.</p>
<pre class="language-svelte"><code class="language-svelte"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>section</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><br>    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>sticky<span class="token punctuation">"</span></span><br>      <span class="token attr-name"><span class="token namespace">use:</span>sticky=</span><span class="token language-javascript"><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">stickToTop</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></span><br>      <span class="token attr-name"><span class="token namespace">on:</span>stuck=</span><span class="token language-javascript"><span class="token punctuation">{</span>handleStuck<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><br>    I use position: sticky!<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token punctuation">/></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span>#<span class="token keyword">if</span> flag<span class="token punctuation">}</span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Me too<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><br>  <span class="token language-javascript"><span class="token punctuation">{</span><span class="token operator">/</span><span class="token keyword">if</span><span class="token punctuation">}</span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>section</span><span class="token punctuation">></span></span></code></pre>
<p>I found that toggling the value of <code>flag</code> would re-insert the node after the bottom sentinel, which could introduce bugs since we expect the bottom sentinel to be the last element in its container. The rendered HTML would look like the following.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stickySentinelTop<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>sticky svelte-1n1qj7a<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stickySentinelBottom<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>Me too<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span></code></pre>
<p>You might not encounter this edge case. In case you do, let's show how we can re-insert the sentinels on changes to the container using a <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">mutation observer</a>.</p>
<h2>Replacing sentinels on mutations</h2>
<p>The Mutation Observer API is similar to the Intersection Observer API — you observe a node and execute a callback when something changes. Our mutation callback will check if the sentinels are still the first and last child and re-insert them if they're not.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> <span class="token function-variable function">mutationCallback</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">mutations</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  mutations<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">mutation</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token keyword">const</span> <span class="token punctuation">{</span><span class="token literal-property property">parentNode</span><span class="token operator">:</span> topParent<span class="token punctuation">}</span> <span class="token operator">=</span> stickySentinelTop<span class="token punctuation">;</span><br>    <span class="token keyword">const</span> <span class="token punctuation">{</span><span class="token literal-property property">parentNode</span><span class="token operator">:</span> bottomParent<span class="token punctuation">}</span> <span class="token operator">=</span> stickySentinelBottom<span class="token punctuation">;</span><br><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>stickySentinelTop <span class="token operator">!==</span> topParent<span class="token punctuation">.</span>firstChild<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      topParent<span class="token punctuation">.</span><span class="token function">prepend</span><span class="token punctuation">(</span>stickySentinelTop<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>stickySentinelBottom <span class="token operator">!==</span> bottomParent<span class="token punctuation">.</span>lastChild<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      bottomParent<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>stickySentinelBottom<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>We don't have to worry about removing the sentinels before re-inserting them, since <code>prepend</code> and <code>append</code> will move them to the new location instead of duplicating the node.</p>
<p>Now that we have our callback, we can initialize the mutation observer and observe our node's parent. We pass an options object to the <code>observe</code> call to indicate that we only care about updates to the list of children.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> mutationObserver <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MutationObserver</span><span class="token punctuation">(</span>mutationCallback<span class="token punctuation">)</span><span class="token punctuation">;</span><br>mutationObserver<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>parentNode<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">childList</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Peachy 🍑. If we try our demo again, we'll see that the sentinels stay in position even when content is added and removed.</p>
<h2>Update and destroy</h2>
<p>One last aspect of actions we haven't touched on is the <code>update</code> and <code>destroy</code> methods. An action can optionally return an object containing these methods. <code>update</code> will be called when any of the parameters passed to the action change, and <code>destroy</code> will be called when the node is removed from the DOM.</p>
<p>Since my demo allows for toggling between sticking to the top and sticking to the bottom, I had to implement <code>update</code> so that we could start observing the other sentinel when <code>stickToTop</code> changes. I also disconnected our observers in the <code>destroy</code> method, but this <a href="https://stackoverflow.com/questions/51106261/should-mutationobservers-be-removed-disconnected-when-the-attached-dom-node-is-r/51106262#51106262">might not be necessary</a> if garbage collection handles it.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">return</span> <span class="token punctuation">{</span><br>  <span class="token function">update</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span>stickToTop<span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    <span class="token comment">// change which sentinel we are observing</span><br>    <span class="token keyword">if</span> <span class="token punctuation">(</span>stickToTop<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>      intersectionObserver<span class="token punctuation">.</span><span class="token function">unobserve</span><span class="token punctuation">(</span>stickySentinelBottom<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      intersectionObserver<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>stickySentinelTop<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>      intersectionObserver<span class="token punctuation">.</span><span class="token function">unobserve</span><span class="token punctuation">(</span>stickySentinelTop<span class="token punctuation">)</span><span class="token punctuation">;</span><br>      intersectionObserver<span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span>stickySentinelBottom<span class="token punctuation">)</span><span class="token punctuation">;</span><br>    <span class="token punctuation">}</span><br>  <span class="token punctuation">}</span><span class="token punctuation">,</span><br><br>  <span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    intersectionObserver<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>    mutationObserver<span class="token punctuation">.</span><span class="token function">disconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<h2>Some caveats</h2>
<p>There's a few caveats to this implementation. Adding raw DOM nodes like this could break certain CSS selectors like <code>:first-child</code>. There's an alternative approach using the <code>rootMargin</code> property of the observer, but this does not let you set any sort of offset position for the sticky element (e.g. <code>top: 1rem</code>). If you don't need to offset the sticky element, using <code>rootMargin</code> may be a better option. You can read more about it at <a href="https://css-tricks.com/an-explanation-of-how-the-intersection-observer-watches/#creating-a-position-sticky-event">CSS Tricks</a>.</p>
<p>We also didn't implement anything for horizontal stickiness. I'll leave that as an exercise for the reader. Our method also requires sticky elements to be the first or last child of their parent. I haven't experimented with how this method handles sticky elements in the middle of a container, and there could be more edge cases.</p>
<h2>Wrapping up</h2>
<p>I hope you learned something about the power of Svelte actions and modern CSS! If you enjoyed the article, let me know on <a href="https://twitter.com/geoffrich_">Twitter</a>.</p>
<h2>Further reading</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/position#Sticky_positioning">MDN docs on sticky positioning</a></li>
<li><a href="https://svelte.dev/docs#use_action">Documentation on Svelte actions</a></li>
<li><a href="https://svelte.school/tutorials/introduction-to-actions">Introduction to Actions</a> (Svelte School)</li>
<li><a href="https://dev.to/virtualkirill/unlocking-the-power-of-svelte-actions-1k29">Unlocking the power of Svelte actions</a> (Kirill Vasiltsov)</li>
<li><a href="https://developers.google.com/web/updates/2017/09/sticky-headers">An event for CSS position:sticky</a> (Google Developers Blog) — this inspired a lot of my approach for this post.</li>
<li><a href="https://css-tricks.com/an-explanation-of-how-the-intersection-observer-watches/#finding-the-position">CSS Tricks</a> on an alternative approach</li>
</ul>

    ]]></content>
    
  </entry>
	
  
  
  
  
  <entry>
    <title>Building a progress stepper</title>
    <link href="https://geoffrich.net/posts/progress-stepper/"/>
    <updated>2020-10-17T00:00:00Z</updated>
    <id>https://geoffrich.net/posts/progress-stepper/</id>
    
    <content type="html"><![CDATA[
      <p>I had a great time solving <a href="https://piccalil.li/blog/challenge-008-progress-stepper/">the latest challenge</a> from Andy Bell's Front-End Challenges Club! I'm looking forward to see how he implemented it, but <a href="/demos/front-end-cc-008/">here's my solution </a> and some notes on my approach.</p>
<h2>Using an ordered list</h2>
<p>This brief seemed like a great use case for an ordered list.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>ol</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>progress<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Your basket<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span><br>    Your details and also some other stuff you probably skipped over<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">aria-current</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>step<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Payment<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Order complete (no turning back now)<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span><span class="token punctuation">></span></span>Crushing regret<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>ol</span><span class="token punctuation">></span></span></code></pre>
<p>I tweaked the copy a bit so I had both long and short text to work with. I also chose to use the <a href="https://www.w3.org/TR/wai-aria-1.1/#aria-current">aria-current</a> attribute to indicate the current step. The checkmark styling will indicate this visually, but this attribute indicates to screen readers what the current step is.</p>
<p>I had previously only seen this attribute used to indicate the current page in a list of navigation links, but this seemed like a great use-case as well. Adding this attribute has NVDA announce &quot;current step Payment&quot; while navigating through the list items. However, I still need to test with other screen readers to make sure this works the same way. If it does not, some visually hidden text may need to be used instead.</p>
<p>At this point, without any styling or scripting, we have a solid HTML foundation that is accessible by default. There's no visual indication as to what the current step is, but we'll get to that next.</p>
<h2>Augmenting the HTML with JavaScript</h2>
<p>With the foundation complete, I wanted to use JavaScript to augment the HTML with the step number text and state of the checkmark (e.g. checked, partially checked, unchecked). This way, the user of this component only needs to write an ordered list with the class &quot;progress&quot; and mark the current step with the <code>aria-current</code> attribute to initialize the stepper. After the JavaScript runs, the initialized list item looks something like this:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>li</span> <span class="token attr-name">data-state</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>previous<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>checkmark<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>svg</span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>      [Truncated]<br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>svg</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>eyebrow<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><br>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>counter<span class="token punctuation">"</span></span> <span class="token attr-name">aria-hidden</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>true<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>01<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>    Step one<br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>title<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Your basket<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">></span></span><br><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>li</span><span class="token punctuation">></span></span></code></pre>
<h3>What to hide from screen readers?</h3>
<p>I set <code>aria-hidden</code> on both the checkmark SVG and the counter text to hide them from screen readers. I believed the checkmark SVG was redundant because I am already indicating the current step with <code>aria-current</code>, and the counter text is redundant because &quot;Step one&quot; will also be read out.</p>
<h3>Using data attributes for styling</h3>
<p>I also set a <code>data-state</code> attribute on the <code>&lt;li&gt;</code> to indicate whether it is a previous, current, or upcoming step. <a href="https://piccalil.li/cube-css/exception/">This is taken from the CUBE CSS methodology</a>, also by Andy Bell. This gives us a hook that can be used by CSS and JavaScript. This attribute is set based on the presence of the <code>aria-current</code> attribute, which ensures that the attribute doesn't get forgotten.</p>
<p>The JavaScript sets it...</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> listItems <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'.progress li'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br><br><span class="token keyword">let</span> hasCurrentBeenReached <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><br>listItems<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item<span class="token punctuation">,</span> idx</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> current <span class="token operator">=</span> item<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">'aria-current'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">if</span> <span class="token punctuation">(</span>current <span class="token operator">===</span> <span class="token string">'step'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    hasCurrentBeenReached <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><br>    item<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token string">'current'</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>hasCurrentBeenReached<span class="token punctuation">)</span> <span class="token punctuation">{</span><br>    item<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token string">'previous'</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><br>    item<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token string">'upcoming'</span><span class="token punctuation">;</span><br>  <span class="token punctuation">}</span><br><br>  item<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token function">getListItemContents</span><span class="token punctuation">(</span>item<span class="token punctuation">.</span>innerHTML<span class="token punctuation">,</span> idx<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>And the CSS uses it!</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.progress li[data-state='previous'] svg</span> <span class="token punctuation">{</span><br>  <span class="token property">opacity</span><span class="token punctuation">:</span> 1<span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token selector">.progress li[data-state='current'] .checkmark</span> <span class="token punctuation">{</span><br>  <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-primary-glare<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span><br><br><span class="token selector">.progress li[data-state='upcoming'] .checkmark</span> <span class="token punctuation">{</span><br>  <span class="token property">border-color</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-secondary<span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--color-secondary-glare<span class="token punctuation">)</span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>I really like using data attributes like this instead of toggling CSS classes. See the CUBE CSS link up above for more on this.</p>
<h3>Generating number words</h3>
<p>There were <a href="https://stackoverflow.com/questions/14766951/convert-digits-into-words-with-javascript">all sort of solutions on Stack Overflow</a> for getting the word for a number, i.e. &quot;Step One&quot;, &quot;Step Two&quot;, etc. I didn't think it was worth the trouble, since the progress stepper will likely only have a few items in it. I ended up using a hard-coded array instead, with a fallback to the number itself if it was missing.</p>
<pre class="language-js"><code class="language-js"><span class="token keyword">const</span> numberWords <span class="token operator">=</span> <span class="token punctuation">[</span><br>  <span class="token string">'one'</span><span class="token punctuation">,</span><br>  <span class="token string">'two'</span><span class="token punctuation">,</span><br>  <span class="token string">'three'</span><span class="token punctuation">,</span><br>  <span class="token string">'four'</span><span class="token punctuation">,</span><br>  <span class="token string">'five'</span><span class="token punctuation">,</span><br>  <span class="token string">'six'</span><span class="token punctuation">,</span><br>  <span class="token string">'seven'</span><span class="token punctuation">,</span><br>  <span class="token string">'eight'</span><span class="token punctuation">,</span><br>  <span class="token string">'nine'</span><span class="token punctuation">,</span><br>  <span class="token string">'ten'</span><span class="token punctuation">,</span><br>  <span class="token string">'eleven'</span><span class="token punctuation">,</span><br>  <span class="token string">'twelve'</span><span class="token punctuation">,</span><br>  <span class="token string">'thirteen'</span><span class="token punctuation">,</span><br>  <span class="token string">'fourteen'</span><br><span class="token punctuation">]</span><span class="token punctuation">;</span><br><br><span class="token keyword">function</span> <span class="token function">getEyebrow</span><span class="token punctuation">(</span><span class="token parameter">index</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br>  <span class="token keyword">const</span> counter <span class="token operator">=</span> <span class="token punctuation">(</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br>  <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string"><br>      &lt;span class="eyebrow"><br>        &lt;span class="counter" aria-hidden="true"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>counter<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">&lt;/span><br>        </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>numberWords<span class="token punctuation">[</span>index<span class="token punctuation">]</span> <span class="token operator">?</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Step </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>numberWords<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Step </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>index <span class="token operator">+</span> <span class="token number">1</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"><br>      &lt;/span><br>    </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<h2>Being careful with JavaScript</h2>
<p>Because JavaScript is setting the number text, I use CSS to remove the default list numbers.</p>
<pre class="language-css"><code class="language-css"><span class="token selector">.progress[data-init]</span> <span class="token punctuation">{</span><br>  <span class="token property">list-style</span><span class="token punctuation">:</span> none<span class="token punctuation">;</span><br><span class="token punctuation">}</span></code></pre>
<p>However, note that I look for the presence of a data attribute. This way, we do not preemptively remove the list styling without something to replace it. Once the attribute is set the number text has been generated, so we can safely remove the list styling.</p>
<pre class="language-js"><code class="language-js">progressList<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>init <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></code></pre>
<p>Also note that doing this removes the list semantics when using VoiceOver, so I set <code>role=list</code> on the parent list <a href="https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html">as mentioned by Scott O'Hara</a>.</p>
<h2>Styling</h2>
<p>The actual styling of the progress stepper was pretty straightforward. Most of it was a lot of absolute or relative positioning of various elements. I styled everything from scratch, so there is probably some redundant styling that would be solved with a better base stylesheet.</p>
<h3>Generated counters?</h3>
<p>I did look into using CSS to generate the counter (&quot;01&quot;, &quot;02&quot;, etc.) using a pseudo-element so that I didn't need to manually add a <code>&lt;span class=&quot;counter&quot;&gt;01&lt;/span&gt;</code> element. However, since I also wanted to prevent it from being read by screen readers, this didn't appear to be possible. Pseudo-elements are read out if their parent element is read out, so I didn't have a way to just hide the pseudo element.</p>
<p>I also ran across <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@counter-style">@counter-style</a> in my research which seemed like it might be a way to generate the &quot;Step One&quot; text using CSS. I didn't experiment with it much, though, since it's only supported in Firefox.</p>
<p>I'm interested to see if Andy's solution uses any CSS-generated counters or if he took a similar approach to mine.</p>
<h2>An interactive demo</h2>
<p>Lastly, I wanted to make it easy to add items to the list and move around the current step to see how the demo behaves in different states. You'll notice a small form at the top of the page that adds items to the list and moves around the current step. I added some small CSS transitions on the progress line that are <em>very</em> satisfying to play around with.</p>
<p>The form is accessible in the sense that everything is labeled properly, but it could probably do more with announcing what changed when buttons are clicked. I didn't have time to test this thoroughly and it wasn't technically part of the challenge, so I skipped it for now.</p>
<h2>Wrapping up</h2>
<p>Thanks for taking the time to read my post! I had a blast with this challenge and can't wait to try the next one.</p>

    ]]></content>
    
  </entry>
	
</feed>
