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.