Build a video call app with Svelte in an afternoon
Tags: svelte, javascript, tutorial, webdev
Published: 2022-01-06 13:34:49

---
title: Build a video call app with Svelte in an afternoon
published: true
description: Embed Daily Prebuilt with custom controls in any Svelte app
tags: svelte, javascript, tutorial, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7io8gxx13bnfl5nxqsl.png
canonical_url: https://www.daily.co/blog/build-a-video-call-app-with-daily-prebuilt-and-svelte/
---

Daily's JavaScript library, [`daily-js`](https://github.com/daily-co/daily-js), is compatible with any frontend framework, which means choosing your stack is really up to you. Today we'll be looking at one of our favourite frameworks at Daily...

![drumroll](https://media.giphy.com/media/MZGH2MEUcfjVvIm2oR/giphy.gif) 

[Svelte](https://svelte.dev/)! 🎉

In today’s tutorial, we’ll rebuild our Daily Prebuilt demo, already built in [React](https://github.com/daily-demos/prebuilt-ui) and [Vue](https://github.com/daily-demos/vue-daily-prebuilt), but this time with Svelte! ✨

Specifically, we’ll cover how to:
- Embed [Daily Prebuilt](https://daily.co/prebuilt) in a Svelte app
- Handle updating your components based on whether you’ve joined a Daily call or not
- Manage your Daily Prebuilt call with a custom control panel

If you want to test the completed version of the demo first, check out the [deployed version](https://svelte-daily-prebuilt.netlify.app/).

---

## What exactly is Svelte?

Svelte is an open-source frontend component framework that can be used as an alternative to other frontend frameworks, like React or Vue. 

It differs from other frameworks based on how it updates your app’s UI. Instead of using a virtual DOM to handle app changes (like React uses), Svelte is instead a compiler that converts app components to JavaScript and inserts any UI changes directly into the DOM itself. 

Or, as the [Svelte docs](https://svelte.dev/blog/svelte-3-rethinking-reactivity) put it:

>“Svelte runs at build time, converting your components into highly efficient imperative code that surgically updates the DOM. As a result, you're able to write ambitious applications with excellent performance characteristics.”

## Who is this tutorial for?

Since this is a Svelte tutorial, the following content will be most helpful for anyone already familiar with Svelte. In case you’re not, we’ll do our best to explain what is unique to Svelte. 

Thankfully, Svelte also has amazing documentation and [interactive tutorials](https://svelte.dev/tutorial/basics) to learn the basics, and we highly recommend giving those a read!

---

## Getting started

To get started locally, clone the [Daily Prebuilt Svelte demo app](https://github.com/daily-demos/svelte-daily-prebuilt) and run the following commands in your terminal:

```bash
npm i
npm run dev
```

You will also need to [create a Daily account](https://dashboard.daily.co/signup) and a [new Daily room](https://dashboard.daily.co/rooms/create) for testing the demo. 

_Note: The [demo README](https://github.com/daily-demos/svelte-daily-prebuilt) includes additional instructions for creating new Daily rooms locally via the app._

Once you have the app running locally, navigate to `http://localhost:5000` in your browser of choice, and you should see the home page.

![home screen](https://www.daily.co/blog/content/images/size/w1600/2021/12/svelte-home-copy-1.png)

---

## `App.svelte`: Determining which view to show

Our app is going to have two possible views:
1. Our home page, which includes a form to join a call
2. The call UI, which includes the Daily Prebuilt embed and our custom call controls

![2 possible views: call UI and home screen](https://www.daily.co/blog/content/images/2021/12/CleanShot-2021-12-16-at-15.32.50@2x.png)

We know we'll need some logic to determine which view should show. To determine this, let’s take a look at our parent [`App`](https://github.com/daily-demos/svelte-daily-prebuilt/blob/main/src/App.svelte) component. `App` will handle deciding whether the `Home` or `Call` component is rendered.

```javascript
// App.svelte
<script>
 import Call from "./screens/Call.svelte";
 import Header from "./components/Header.svelte";
 import Home from "./screens/Home.svelte";
 
 let currentScreen = "home"; //  || 'call'
 let url;
 let userName;
 
 const handleJoinCall = ({ detail }) => {
   currentScreen = "call";
   // set component vars with form submission values
   url = detail.url;
   userName = detail.name;
 
   // save in local storage
   localStorage.setItem("svelte-prebuilt-url", url);
   localStorage.setItem("svelte-prebuilt-name", userName);
 };

 const handleLeaveCall = () => {
   currentScreen = "home";
 };
</script>
 
<svelte:head>
 <script crossorigin src="https://unpkg.com/@daily-co/daily-js"></script>
</svelte:head>
 
<div class="wrapper">
 <Header />
 {#if currentScreen === "home"}
   <Home on:submit={handleJoinCall} />
 {:else}
   <Call {userName} {url} on:left={handleLeaveCall} />
 {/if}
</div>
```

Let’s step through this:
- In the script tag, we start by importing the components we’ll be using (`Call`, `Home`, and `Header`)
- Then, we declare variables that will be used in this component but are assigned later on.
- Next, we define `handleJoinCall`, which we'll describe in more detail below. In short, it sets our `currentScreen` variable to `call`.
- We then define `handleLeaveCall`, which simply resets `currentScreen` to `home`.
- Lastly, we import our `daily-js` script tag to make the `daily-js` library available to the rest of the app.

Now let’s specifically look at the markdown in `App` to see how we render our components:


```javascript
// App.svelte
<div class="wrapper">
 <Header />
 {#if currentScreen === "home"}
   <Home on:submit={handleJoinCall} />
 {:else}
   <Call {userName} {url} on:left={handleLeaveCall} />
 {/if}
</div>

```

In Svelte, you can conditionally render components based on your JavaScript values using an [`if` statement](https://svelte.dev/tutorial/else-if-blocks). 

We know we always want to render our `Header`, but we only want to render the `Call` component if the user is trying to join a call, i.e. the `currentScreen` equals `call`. 

This is where we can use Svelte’s `if` syntax:
- `{#if currentScreen === "home"}` to render the `Home` screen
- `{:else}` we can show the `Call` component.

And don’t forget to close your `if` block with `{/if}` 

### Listening for custom events

Another handy feature of Svelte is being able to dispatch [custom events](https://svelte.dev/tutorial/event-forwarding) that can be forwarded to other components. 

Since the `Home` component has a form to join calls, we want to call `handleJoinCall` (defined in `App`) when the form’s `submit` event is dispatched. 

Similarly, when you’re in a call and go to leave the call, we want to listen for the custom `left` event we'll define in `Call`.

We handle both situations by adding a custom listener on each component, like so:
`<Call on:left={handleLeaveCall} />`

Notice how it says `on:left`? This will listen for any `left` events dispatched. To listen to other custom events you've defined, you can just update the event name, such as `on:fancy-event-name`.

One other thing to note is being able to pass variables to child components. In Svelte, we can pass the `userName` and `url` values as [props](https://svelte.dev/tutorial/declaring-props) to the `Call` component like so:
 `<Call {userName} {url} />`

_Note: Since the prop name and variable being passed use the same name, we can use the shorthand notation. That is to say, `{url}` is shorthand for `url={url}`._

These properties can then be made available in `Call` like so:


```javascript
// Call.svelte
export let url;
export let userName;
```

## Honey, I’m `Home.svelte` 

![Home screen again](https://www.daily.co/blog/content/images/size/w1600/2021/12/svelte-home-copy.png) 

By default, `App` will render the `Home` component first. 

Our `Home` component contains the main content for our app’s home screen. The code for it is a bit longer than `App` so we’ll look at the most important sections.

```javascript
// Home.svelte
<script>
 import { createEventDispatcher, onMount } from "svelte";
 import api from "../api";
 
 const dispatch = createEventDispatcher();
 ...
```

At the top of our component, we import Svelte’s [`createEventDispatcher` method](https://svelte.dev/tutorial/component-events) and assign it to the variable `dispatch`. This method is what we’ll use for dispatching our custom events discussed above. 

Now let’s jump to our HTML and focus on the `form`.

```javascript
// Home.svelte
<div class="home-screen">
 <h2>Daily Prebuilt Svelte demo</h2>
 <p>Start demo with a new unique room or paste in your own room URL</p>
…
 <form on:submit={goToCall}>
   <label for="name">Name</label>
   <input id="name" type="text" required bind:value={name} />
   <label for="url">Daily URL</label>
   <input
     id="url"
     type="text"
     required
     bind:value={url}
     placeholder="https://your-domain.daily.co/room-name"
   />
   <input type="submit" value="Join call" />
 </form>
</div>
```

Here, we have a native HTML `<form>` element with the `submit` handler using `goToCall`:

`<form on:submit={goToCall}>`

_Note: This is not a custom event since forms have a native `submit` event._

The form has two inputs for a username and Daily room URL. To make each of these input values available in our component’s JavaScript, we can bind each value to a variable declared at the top of the component.

For example, the username input value will be bound to the variable `name`, which is declared at the top of the file:


```javascript
// Home.svelte
<script>
   let name;
</script>

...
<form on:submit={goToCall}>
    ...
	<input id="name" type="text" required bind:value={name} />
    ...
</form>
```

This means when the form’s submit event occurs and the `goToCall` method is called, we can use the input values via our bound variables (e.g `name`). 

This also makes the input values available to forward to the `App` component via the `dispatch` method we defined before like so:

```javascript
// Home.svelte
const goToCall = (e) => {
   e.preventDefault();
    
   dispatch("submit", {
     name,
     url,
   });
 };

```

In `goToCall`, we first prevent the form from refreshing the page with `e.preventDefault()`.

Then we use our `dispatch` method to [forward](https://svelte.dev/tutorial/event-forwarding) the `submit` event to our `App` component. Both `name` and `url` (our variables bound to the inputs) are passed as options to make those values available to `App`, as well. 

If you recall from `App`, the `Home` component has an event listener on it for `submit`, which calls the `handleJoinCall` method. 

`<Home on:submit={handleJoinCall} />`

When our dispatched `submit` event registers in `App`, it will call `handleJoinCall`. 


```javascript
// App.svelte
 const handleJoinCall = ({ detail }) => {
   currentScreen = "call";
   // set component vars with form submission values
   url = detail.url;
   userName = detail.name;
 
   // save in local storage
   localStorage.setItem("svelte-prebuilt-url", url);
   localStorage.setItem("svelte-prebuilt-name", userName);
 
   error = null;
 };

```

In `handleJoinCall` we update `currentScreen` to equal `call`. This will cause the `Call` component to show instead of `Home`. We then set our `url` and `userName` variables to the values passed from the form and save them in local storage, as well. 

Now that all the `Home` form's information is shared with `App` — who also shares it with `Call` — we can move on to setting up our call UI.

## Call on line 1, please

So far we’ve set up our `App` component and our `Home` screen. Now let’s build our Daily call in [`Call.svelte`](https://github.com/daily-demos/svelte-daily-prebuilt/blob/main/src/screens/Call.svelte).

![Diagram of how the Call component is split up](https://www.daily.co/blog/content/images/size/w1600/2021/12/svelte-call-copy.png)

Let’s start with the `Call` component's HTML markdown this time.

```javascript
// Call.svelte
<div
 class={meetingState === "joined-meeting"
   ? "call-screen"
   : "call-screen prejoin"}
>
 <div id="container" />
 <Controls
   {url}
   {meetingState}
   {stats}
   on:toggle-camera={toggleCamera}
   on:toggle-mic={toggleMic}
   on:toggle-screen-share={toggleScreenShare}
   on:fullscreen={goFullscreen}
   on:toggle-local-video={toggleLocalVideo}
   on:toggle-remote-video={toggleRemoteVideo}
 />
</div>

```

We have a container `div` with two children:
- Another `div`, which is where we’ll embed Daily Prebuilt
- Our `Controls` component, which receives the room `url`, `meetingState`, and call `stats` as props. It also has a list of custom event listeners added for each of the control’s buttons.

Let’s focus on the Daily call to start.

### Embedding Daily Prebuilt in your Svelte app

Svelte has a handy [`onMount` lifecycle method](https://svelte.dev/tutorial/onmount) that gets called when the component is first rendered.


```javascript
// Call.svelte

import { onMount } from "svelte";
 
onMount(() => {
   // assume if the Call component is showing, we should join
   initializeDaily();
 });

```

We know the `Call` component mounts when the form is submitted, so we want to initialize the call as soon as `Call` renders. We can do this by calling `initializeDaily` on mount.


```javascript
// Call.svelte

 const initializeDaily = async () => {
   …
   // select container element to embed Daily iframe in
   const container = document.getElementById("container");
   // create Daily iframe
   callFrame = window.DailyIframe.createFrame(container, {
     iframeStyle: IFRAME_OPTIONS,
     showLeaveButton: true,
     url,
     userName,
   });
 
   callFrame.on("joining-meeting", updateMeetingState);
   callFrame.on("joined-meeting", updateMeetingState);
   callFrame.on("left-meeting", handleLeftMeeting);
   callFrame.on("error", updateMeetingState);
 
   // set up interval for retrieving current network stats
   interval = setInterval(() => getNetworkStats(), 5000);
 
   // let the local user join the call, which will cause
   // the call to be displayed in our app UI
   await callFrame.join();
 };

```

Stepping through this `initializeDaily` function: 
1. We first select our `div` element that will be the Daily Prebuilt iframe’s container: 

`const container = document.getElementById("container");`

2. Next we create our Daily Prebuilt iframe with the [`createFrame` method](https://docs.daily.co/reference/daily-js/factory-methods/create-frame) via `daily-js`, and pass the container `div` and some call options to it.

`window.DailyIframe.createFrame(container, options)`

_Recall: `window.DailyIframe` exists because we imported the `daily-js` script in `App`._

3. Next, we add some Daily event listeners so we can update our UI if the local user joins a call, leaves, or there’s a critical error. We’ll explain this a bit more below.

e.g. `callFrame.on("joined-meeting", updateMeetingState);`

4. Then we set up an interval to get the call’s network stats every 5 seconds so we can display it in our `Control` panel. (We won’t go into more detail about this, but [ask us if you need help](https://www.daily.co/contact/support)!)

`interval = setInterval(() => getNetworkStats(), 5000);`

5. And, finally, we use Daily’s [`join` method](https://docs.daily.co/reference/daily-js/instance-methods/join) to actually join the call. 🎉

`await callFrame.join();`

![Transitioning from the home screen to video call](https://www.daily.co/blog/content/images/2021/12/CleanShot-2021-12-30-at-11.14.50.gif)

## Adding our custom control panel

As Bruce Springsteen once said, “Honey, I want the heart, I want the soul, I want control right now,” so let’s do just that and add a little more control to our app.

`daily-js` provides instance methods to programmatically do anything you can already do via Daily Prebuilt's UI. This gives a bit more flexibility to how you want to set up your own app’s UI.

For example, if you want to add a big “Mute” button to your UI, you can! Let’s take a look at how.

### Adding a Mute button to toggle local audio

As mentioned, our `Call` component has a bunch of custom event listeners added to [`Controls`](https://github.com/daily-demos/svelte-daily-prebuilt/blob/main/src/components/Controls.svelte). This means all the actual Daily logic can stay contained in our `Call` component. The `Controls` component is basically just UI to dispatch the custom events.


```javascript
// Call.svelte

<Controls
   …
   on:toggle-mic={toggleMic}
```

In `Controls`, we have a button to mute the local user:

```javascript
// Controls.svelte

<button on:click={handleToggleMicClick}>
    <span>
    	Toggle mic
        <img src="/images/mic.svg" alt="Microphone" />
    </span>
</button>

```

On click, this button calls `handleToggleMicClick`. That method will then dispatch our `toggle-mic` custom event:

`const handleToggleMicClick = () =>  dispatch("toggle-mic");`

Looping back to `Call`, the `toggle-mic` custom event that is forwarded calls `toggleMic` (`on:toggle-mic={toggleMic}`), a method defined in `Call`. 

The `toggleMic` method is what contains our `daily-js` interaction.


```javascript
// Call.svelte 

const toggleMic = () => {
   if (!callFrame) {
     logError(noCallFrameError);
     return;
   }
   const localVideo = callFrame.localAudio();
   callFrame.setLocalAudio(!localVideo);
 };

```

If there is no Daily `callFrame` – defined in `initializeDaily` – we do nothing. This shouldn’t ever happen but, you know, bugs. 🐛

Next, we get our current local audio state (muted or unmuted) via the [`localAudio` Daily method](https://docs.daily.co/reference/daily-js/instance-methods/local-audio):

`const localVideo = callFrame.localAudio();`

Whatever the current value is, we want to make it the opposite. To do this, we can use the [`setLocalAudio` instance method](https://docs.daily.co/reference/daily-js/instance-methods/set-local-audio) and set our local audio to the inverse boolean value. 

`callFrame.setLocalAudio(!localVideo);`

Once that’s called, your unmuted audio will mute, or vice versa! 💪

We won’t cover all the buttons in the control panel since they all follow a similar pattern, but if you need help, [please let us know](https://www.daily.co/contact/support)! 🤗

![Muting yourself with a custom Mute button](https://www.daily.co/blog/content/images/2021/12/CleanShot-2021-12-30-at-11.20.49.gif)

## Appetite for (call) destruction

To understand how the Daily event listeners work a bit more, let’s use the [`left-meeting` event](https://docs.daily.co/reference/daily-js/events/meeting-events#left-meeting) as an example. 

In `Call`, we added an event listener for `left-meeting`, which calls a callback method (`handleLeftMeeting`) when triggered:


```javascript
// Call.svelte

callFrame.on("left-meeting", handleLeftMeeting); // in initializeDaily()

 const handleLeftMeeting = (e) => {
   updateMeetingState(e);
   if (interval) clearInterval(interval);
   dispatch("left");
 };

```

The callback, `handleLeftMeeting`, gets called when the local user clicks the `Leave` button via Daily Prebuilt’s UI. It then dispatches a custom event (`left`) that `App` is listening for. 

It does a couple other clean up tasks as well, but we won't worry about that for now. Essentially, it resets our app’s state.

If you recall, `App` has a listener on the `Call` component for a custom `left` event:
`<Call {userName} {url} on:left={handleLeaveCall} />`

`handleLeaveCall` gets called, which resets our `currentScreen` value to `home`:

`const handleLeaveCall = () => currentScreen = "home";`

Once `currentScreen` gets reset to `home`, `App` will render the `Home` component instead of `Call`.

![Leaving the call and going back to the home screen](https://www.daily.co/blog/content/images/2021/12/CleanShot-2021-12-30-at-11.24.41.gif)

We’ve come full circle back home! 🍩

![full circle](https://media.giphy.com/media/tDJYUdv3osmDs5HlyU/giphy.gif) 

### Cleaning up after the party

Now that we have reset our state to get back to the home screen, we still have a little leftover clean up to do.

If you recall, we added some Daily event listeners to our `callFrame`. 

As a final step, we can turn off those event listeners and destroy our Daily `callFrame`. It will be recreated when the next call is joined so we don't need it hanging around after leaving this call.

```javascript
// Call.svelte
import { onDestroy } from "svelte";

onDestroy(() => {
    if (callFrame) {
      // remove event listeners
      callFrame.off("joining-meeting", updateMeetingState);
      callFrame.off("joined-meeting", updateMeetingState);
      callFrame.off("left-meeting", handleLeftMeeting);
      callFrame.off("error", handleError);

      // destroy Daily callframe after call ends
      callFrame.destroy();
    }
  });
```

Svelte provides an `onDestroy` [lifecycle method](https://svelte.dev/tutorial/ondestroy) that will be triggered when the `Call` component is destroyed.

In `onDestroy`, we can turn off our Daily event listeners and destroy the `callFrame` with Daily's [`destroy` method](https://docs.daily.co/reference/daily-js/instance-methods/destroy).

---

## Wrapping up

We hope this tutorial helps you get started with building your own Daily video apps in Svelte. To learn more about Daily Prebuilt, check out our [other Daily Prebuilt blog posts](https://www.daily.co/blog/tag/daily-prebuilt/). 

If you’d like to build something more custom, keep an eye out on [Twitter](https://twitter.com/trydaily) for our next Svelte tutorial! 👀