Anyone who has ever built an app with vanilla JS knows how complicated it can be to not only smoothly fade elements in but also let them disappear smoothly. Sure, you can simply animate the transparency and toggle the pointer events on and off, but sometimes you want to completely remove an element from the DOM. This is disproportionately complicated with native technologies.

With Svelte, the world looks completely different. Here, buttery smooth transitions are a standard feature. This allows you to focus on the important aspects of your app and avoid headaches!

Today, we will take a closer look at so-called “Svelte transitions” while building a simple sidebar.

The sidebar should be its own component, which we create under src/lib/components/. In case you didn’t know: The lib folder comes with a free alias “$lib”, so you can later import the sidebar from anywhere with “$lib/components/Sidebar.svelte”.

<!-- Sidebar.svelte -->
<script>
  let isOpen = true;
</script>

{#if isOpen}
  <aside>
    <p>Hello from sidebar</p>
  </aside>
{/if}

To see what we’re doing, we import the sidebar into our +page.svelte:

<!-- +page.svelte -->
<script>
  import Sidebar from "$lib/components/Sidebar.svelte"
</script>

<Sidebar />

BlogPost_svelte-transitions_image_1.png

Next, we style our sidebar so that it is fixed on the left side and casts a shadow. We also give it a background color and some padding:

<!-- Sidebar.svelte -->
<script>
  let isOpen = true;
</script>

{#if isOpen}
  <aside>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
  aside {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    background: white;
    padding: 1rem;
    box-shadow: 0 0 1rem rgba(0, 0, 0, 0.25);
  }
</style>

BlogPost_svelte-transitions_image_2.png

Okay, now let’s make sure the sidebar can actually be opened and closed. To do this, we create and export two functions:

<!-- Sidebar.svelte -->
<script>
	let isOpen = true;
	  
	export function open() {
	  isOpen = true;
	}
	
	export function close() {
		isOpen = false;
	}
</script>

...

Now we need a button at the top right to close the sidebar.

<!-- Sidebar.svelte -->
<script>
  ...
</script>

{#if isOpen}
  <aside>
    <button on:click={close}>X</button>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
  aside {
    ...
    display: flex; /* Important so we can position the button with margin */
    flex-direction: column;
  }

  button {
    margin-left: auto;
  }
</style>

BlogPost_svelte-transitions_image_3.png

When we click the X now, the sidebar disappears.

What comes next may initially confuse you, but it’s actually quite simple:

We bind the sidebar component to a variable so that we can access its methods (the exported functions). This is necessary so that Svelte knows which instance of our component we are addressing, as we could theoretically embed it multiple times.

<!-- +page.svelte -->
<script>
  import Sidebar from "$lib/components/Sidebar.svelte"
  
  let sidebar;
</script>

<Sidebar bind:this={sidebar} />

I like to name the variable the same as the component, just lowercase. However, the name itself is insignificant.

Now we can add a button that calls the open function of the sidebar:

<!-- +page.svelte -->
<script>
  import Sidebar from "$lib/components/Sidebar.svelte"
  
  let sidebar;
</script>

<button on:click={sidebar.open}>Open Sidebar</button>

<Sidebar bind:this={sidebar} />

Now you can open and close the sidebar. And before we forget, we should set the isOpen variable in the component to false.

<!-- Sidebar.svelte -->
<script>
  let isOpen = false;
</script>

...

So, now we FINALLY get to the transitions. Now we will ensure that the sidebar opens and closes super smoothly.

To do this, we import the fly transition:

<!-- Sidebar.svelte -->
<script>
  import { fly } from 'svelte/transition';
  
  ...
</script>

...

Svelte offers us seven types of transitions: fadeblurflyslidescaledraw, and crossfade.

In fact, there is the possibility to write custom transitions, which is not even difficult. However, experience has shown that you almost never need to do this, as the seven functions already cover everything. In 90% of all cases, you will use fly, fade, scale, and less often slide.

Fly means that the element “flies” into the picture with “transform: translate”. In the start position, it is by default transparent and then becomes visible (when disappearing, it is exactly the opposite).

And this is how we apply the transition to our sidebar:

<!-- Sidebar.svelte -->
<script>
  ...
</script>

{#if isOpen}
  <aside transition:fly={{ x: "-100%" }}>
    <button on:click={close}>X</button>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
	...
</style>

So we write “transition:” and then the name of the desired transition in the start tag of our animating element.

As a value, we set an object and, to ensure Svelte recognizes it as JavaScript, we enclose it with additional curly braces. With fly, we can set the following attributes in addition to the x-position: delay, duration, easing, y, and opacity.

Opacity is preset to 0, but for our purposes, we set it to 1 here so that the sidebar really only moves in from the side and does not become opaque on the way. We also load a nicer easing function:

<!-- Sidebar.svelte -->
<script>
  import { cubicOut } from 'svelte/easing';
  ...
</script>

{#if isOpen}
  <aside transition:fly={{ x: "-100%", opacity: 1, easing: cubicOut }}>
    <button on:click={close}>X</button>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
	...
</style>

BlogPost_svelte-transitions_image_4.gif

At the moment, the same transition is used for appearing and disappearing, but you can use “in:” and “out:” instead of “transition:”. Suppose you want a slow transition for appearing, while disappearing should only take 100ms. Then you would write the following:

<!-- Sidebar.svelte -->
<script>
  ...
</script>

{#if isOpen}
  <aside
   in:fly={{ x: "-100%", opacity: 1, easing: cubicOut, duration: 500 }}
   out:fly={{ x: "-100%", opacity: 1, easing: cubicOut, duration: 100 }}
  >
    <button on:click={close}>X</button>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
	...
</style>

Oh, it would of course be nice if the sidebar stood out with a darkened background and a click on it would close the sidebar.

To do this, we create and style the background and also add a transition to it. This time we use fade.

<!-- Sidebar.svelte -->
<script>
  import { fly, fade } from 'svelte/transition';
	...
</script>

{#if isOpen}
  <button
     in:fade={{ easing: cubicOut, duration: 500 }}
     out:fade={{ easing: cubicOut, duration: 100 }}
    class="backdrop"
    on:click={close}
  ></button>
  <aside
   in:fly={{ x: "-100%", opacity: 1, easing: cubicOut, duration: 500 }}
   out:fly={{ x: "-100%", opacity: 1, easing: cubicOut, duration: 100 }}
  >
    <button on:click={close}>X</button>
    <p>Hello from sidebar</p>
  </aside>
{/if}

<style>
	...
	
  .backdrop {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.1);
    border: none;
  }
</style>

BlogPost_svelte-transitions_image_5.gif

Isn’t that amazingly simple? This effectively took us 10 minutes and the code is clear and very easy to adjust.

If you want to learn more details about Svelte transitions, you probably won’t get around reading the documentation: https://svelte.dev/docs/svelte-transition

Otherwise, try it out and definitely try creating your own transition at least once. As mentioned, this is hardly necessary in practice, but important to gain a deeper understanding of what goes on behind the scenes. This blog post explains everything in detail: https://css-tricks.com/making-your-first-custom-svelte-transition/

Here you can find all the code we wrote together: https://tinyurl.com/svelte-transitions