Introduction to Svelte and its core concepts

In this tutorial, I introduce you to Svelte, a compiler for building front-end user interfaces and applications. Learn what Svelte is, why you should use it, and get hands-on experience by using it to build a reusable search component that filters an array of strings. By following along, you will appreciate the core concepts of Svelte including reactivity, two-way binding, template syntax, scoped styles, slots, and more.

Created by Rich Harris, Svelte is an open source project written in TypeScript and published to npm that you can use to build web applications. The latest major version of Svelte is version 3, released on April 21, 2019.

Estimated time

This tutorial takes about 20 minutes to complete.

Prerequisites

To follow this tutorial, ensure that you have Node.js and npm (version 5.2 or greater) installed. For MacOS, the prerequisites can be installed using Homebrew.

Watch the video

In this video, I walk you through the content covered in this tutorial, giving you an introduction to Svelte’s concepts and showing you how to use it.

Why Svelte?

Compared to other frontend frameworks and libraries like React, Angular or Vue, Svelte can build (i.e. compile) a web application with a significantly smaller bundle size. Instead of using a virtual DOM which has a fixed runtime cost, Svelte performs most optimizations at the build step. As a result, less JavaScript is shipped to end users, enabling faster download and start-up times.

In addition, Svelte uses a concise syntax that enables you to write less boilerplate code. Svelte components follow the single file component (SFC) design in which business logic (JavaScript) is collocated with styles (CSS) and markup (HTML-like templating syntax).

For example, the structure of a Svelte component is composed of the following:

<!-- Component.svelte -->

<script>
  /* JavaScript ("business logic") */
</script>

<style>
  /* CSS (scoped to the component) */
  h1 {
    font-size: 2rem;
  }
</style>

<!-- Markup -->
<h1>Hello world</h1>

Scaffold a new project

For modern web application development, it is highly recommended to use a module bundler like Rollup or Webpack. In this tutorial, I use the official Svelte Rollup template.

Run the following command in a command line interface (CLI) to scaffold a new project:

$ npx degit sveltejs/template svelte-app

You should see a new folder called “svelte-app” on your local machine containing the downloaded template. Inside the folder, install the project dependencies from npm:

$ cd svelte-app
$ npm install

After you’ve installed the dependencies, execute npm run dev to start the project in development mode. Visit http://localhost:5000 to view the app.

$ npm run dev

Any changes you make to files in the src folder should automatically reload the app running on http://localhost:5000. This makes the development process more efficient.

Build a search component

Create a new component called Search.svelte in the src folder.

$ touch src/Search.svelte

Include the following content in the file.

<!-- Search.svelte -->
<script>
  let value = "";
</script>

<input type="search" bind:value />

The value variable declared in the script block will be updated when typing into the input element.

The bind: directive illustrates the concept of two-way binding. In this case, Svelte automatically updates value as the user types into the input element.

After saving the file, however, the app does not reload because the component has not been instantiated in the application.

Replace the existing content within App.svelte with the following:

<!-- App.svelte -->
<script>
  import Search from "./Search.svelte";
</script>

<Search />

Having imported and instantiated the Search component, save the App.svelte file. Now, when reloading the application, you should see the input element in the browser.

Next, we’ll define properties for the Search component that can be controlled in App.svelte.

Define and export variables named autofocus and data:

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  let value = "";
</script>

<input type="search" bind:value />

For the exported variables to be defined by a consumer, they must be initialized with the let statement. You can assign default values for each of the exported variables in case no value is specified.

By default, autofocus is initialized as false while data is an empty array.

In App.svelte, you can pass values as attributes to the instantiated Search component.

<!-- App.svelte -->
<script>
  import Search from "./Search.svelte";

  const data = [
    "Bare Metal Server",
    "Blockchain Platform",
    "Db2 Warehouse",
    "Db2",
    "Metrics Server",
    "Node.js",
    "Virtual Machine",
    "Virtual Private Server",
  ];
</script>

<Search autofocus="{true}" data="{data}" />

Using Svelte’s attribute shorthand syntax, you can revise the <Search> code to:

<Search autofocus {data} />

In the Search component, programmatically focus the input element if autofocus is true.

Define a variable called input and pass it to the input element using the bind:this directive. You can only access the reference to the input element after it is rendered to the Document Object Model (DOM).

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  let input = undefined;
  let value = "";
</script>

<input bind:this="{input}" type="search" bind:value />

Then, import the onMount lifecycle method from Svelte. Inside the onMount function, you can invoke the .focus() method on the input element if autofocus is enabled.

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  import { onMount } from "svelte";

  let input = undefined;
  let value = "";

  onMount(() => {
    if (autofocus) {
      input.focus();
    }
  });
</script>

<input bind:this="{input}" type="search" bind:value />

After saving Search.svelte, the input element should be focused on reload.

Now, let’s render data in the Search component as an unordered list with some basic styling. By default, if value is an empty string, it will list all items from the array in the UI.

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  import { onMount } from "svelte";

  let input = undefined;
  let value = "";

  onMount(() => {
    if (autofocus) {
      input.focus();
    }
  });
</script>

<style>
  ul {
    list-style: none;
  }
</style>

<input bind:this="{input}" type="search" bind:value />

<ul>
  {#each data as item}
  <li>{item}</li>
  {/each}
</ul>

The #each templating syntax is used to iterate through an array. The CSS rule for the ul element defined in the style block is scoped to the component. In other words, the rules defined for ul in Search.svelte will not be applied to a ul element outside of the component.

In your application, you should now see the data array rendered as a list below the input element.

Now, let’s filter the data list based on the value.

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  import { onMount } from "svelte";

  let input = undefined;
  let value = "";

  onMount(() => {
    if (autofocus) {
      input.focus();
    }
  });

  $: filtered = data.filter((item) =>
    item.toLowerCase().includes(value.toLowerCase())
  );
</script>

<style>
  ul {
    list-style: none;
  }
</style>

<input bind:this="{input}" type="search" bind:value />

<ul>
  {#each data as item}
  <li>{item}</li>
  {/each}
</ul>

Use the label syntax (e.g. $:) to denote filtered as a reactive assignment.

$: filtered = data.filter((item) =>
  item.toLowerCase().includes(value.toLowerCase())
);

For any change to data, the value of filtered is re-computed. Use the .filter() Array method on data to avoid mutating the data value.

For the purposes of this basic example, we will perform a direct string comparison to filter the list.

In the #each block, replace data with filtered.

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  import { onMount } from "svelte";

  let input = undefined;
  let value = "";

  onMount(() => {
    if (autofocus) {
      input.focus();
    }
  });

  $: filtered = data.filter((item) =>
    item.toLowerCase().includes(value.toLowerCase())
  );
</script>

<style>
  ul {
    list-style: none;
  }
</style>

<input bind:this="{input}" type="search" bind:value />

<ul>
  {#each filtered as item}
  <li>{item}</li>
  {/each}
</ul>

Preview your saved changes. Now, the Search component should filter the list as you type into the search input.

How can you make this component more reusable?

One way is to defer the list rendering to the consumer.

You can achieve this by wrapping the ul element in the slot element with filtered as an attribute.

<!-- Search.svelte -->
<script>
  export let autofocus = false;
  export let data = [];

  import { onMount } from "svelte";

  let input = undefined;
  let value = "";

  onMount(() => {
    if (autofocus) {
      input.focus();
    }
  });

  $: filtered = data.filter((item) =>
    item.toLowerCase().includes(value.toLowerCase())
  );
</script>

<style>
  ul {
    list-style: none;
  }
</style>

<input bind:this="{input}" type="search" bind:value />

<slot {filtered}>
  <ul>
    {#each filtered as item}
    <li>{item}</li>
    {/each}
  </ul>
</slot>

When the Search component is invoked, the default list is rendered by default. However, the consumer can render their own markup by defining child elements like so:

<!-- App.svelte -->
<script>
  import Search from "./Search.svelte";

  const data = [
    "Bare Metal Server",
    "Blockchain Platform",
    "Db2 Warehouse",
    "Db2",
    "Metrics Server",
    "Node.js",
    "Virtual Machine",
    "Virtual Private Server",
  ];
</script>

<Search autofocus {data} let:filtered>
  <ul>
    {#each filtered as item}
    <li>{item}</li>
    {/each}
  </ul>
</Search>

The filtered slot attribute is accessed via the let: directive. The slot element in Search is replaced with the child element from App.svelte.

Wrapping up

This tutorial demonstrated basic concepts – reactivity, defining component properties, directives, slots – that are fundamental to Svelte.

You can build the application by running the following:

$ npm run build

The Rollup module bundler compiles the Svelte application for production.

For the next steps, delve into the official Svelte API documentation to learn more about component directives, forwarded events, lifecycle methods, state management and more.